Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shorthand for fns that immediately match #1577

Open
nikomatsakis opened this issue Apr 8, 2016 · 26 comments
Open

Shorthand for fns that immediately match #1577

nikomatsakis opened this issue Apr 8, 2016 · 26 comments
Labels
T-lang Relevant to the language team, which will review and decide on the RFC.

Comments

@nikomatsakis
Copy link
Contributor

There is a desire to have some sort of shorthand for functions that immediately match on their argument. Currently, one must write:

fn foo(x: Type) { match x { ... } }

or

|x| match x { ... }

It would be nice to be able to elide the match (and perhaps not even give a name to the parameter x). Many functional languages like Haskell and Scala have these sorts of shorthands.

Related RFCs:

@nrc nrc added the T-lang Relevant to the language team, which will review and decide on the RFC. label Aug 18, 2016
@mark-i-m
Copy link
Member

It does seem like a bit of a special case though... What if match was defined to be an inlined function call... That is match could be sugar for a function that does the match returns the result.

@Stebalien
Copy link
Contributor

@mark-i-m You mean my_iterator.filter(match { 0 => true, _ => false }) (no closure)?

@mark-i-m
Copy link
Member

Yes, or rather an implicit closure

On Aug 29, 2016 9:38 AM, "Steven Allen" [email protected] wrote:

@mark-i-m https://github.com/mark-i-m You mean my_iterator.filter(match
{ 0 => true, _ => false }) (no closure)?


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#1577 (comment),
or mute the thread
https://github.com/notifications/unsubscribe-auth/AIazwCmwL5q6s-80Au8MFe08W5CPXgbhks5qku7QgaJpZM4IDY2s
.

@mark-i-m
Copy link
Member

Now, that I think about... I think we would have to clarify when match
would return the closure vs execute the closure, which could be inelegant.

On Aug 29, 2016 9:51 AM, "Mark Ishak Mansi" [email protected] wrote:

Yes, or rather an implicit closure

On Aug 29, 2016 9:38 AM, "Steven Allen" [email protected] wrote:

@mark-i-m https://github.com/mark-i-m You mean my_iterator.filter(match
{ 0 => true, _ => false }) (no closure)?


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#1577 (comment),
or mute the thread
https://github.com/notifications/unsubscribe-auth/AIazwCmwL5q6s-80Au8MFe08W5CPXgbhks5qku7QgaJpZM4IDY2s
.

@nikomatsakis
Copy link
Contributor Author

just writing match { <arms> } would indeed work as syntactic sugar for |x| match x { <arms> }. I don't believe there is an ambiguity there, because we don't currently permit a block to appear as the match expression (I think). Whether we'd want to do it idk. I don't hate it, actually, but I might prefer to have a syntax where you can use _ as a placeholder, like scala, and hence you would write match _ { <arms> } in place of |x| match x { <arms> } -- but you could also write _ + 5 in place of |x| x + 5 and _ + _ in place of |x, y| x + y. The challenge here tends to be defining the extent of the closure, though, which is kind of ambiguous.

@netvl
Copy link

netvl commented Aug 31, 2016

but I might prefer to have a syntax where you can use _ as a placeholder, like scala

This thing would be simply wonderful! I'm writing Scala on my main job, and every time I return to writing Rust I miss the shorthand syntax for closures very badly.

@mark-i-m
Copy link
Member

I am wondering if there would be any need/way to also have a shorthand for move |x| expr.

@nikomatsakis
Copy link
Contributor Author

@mark-i-m yes, a good point. of course we could permit one to just use move, though it reads more awkwardly.

@mark-i-m
Copy link
Member

mark-i-m commented Sep 1, 2016

Hmmm... yes, but it does seem to mar the usual elegance of rust syntax... I am beginning to become a bit opposed to the idea of adding special syntax...

@mdinger
Copy link
Contributor

mdinger commented Jan 8, 2017

#1612 is similar as are these two forum threads: older and the newer.

@burdges
Copy link

burdges commented Jan 8, 2017

At present a code block can appear as a match expression, like if you write a do .. while loop as while { body; condition } { }, but a match block with => would presumably be another animal entirely.

Rust has issues with currying, right? Just with kinds or even more basic? I've encountered reborrowing problems with doing say

let s : &`static str = match (x,y) { 
    (None,None) => ..,
    (Some(_),None) => ..,
    (None,Some(_)) => ..,
    (Some(_),Some(_)) => ..,
}

I donno if they could be fixed by trying harder though, but I doubt tuples work either. As a result, we might be stuck as one parameter for this. If so, then closure notations match _ { .. } alone might suffice, which makes everything easier.

If you do need notation for non-closures, then maybe

fn f(Ok(x): Result<X,Y>) => T { .. }
fn f(Err(y): Result<X,Y>) => T { .. }

but obviously this would benefit form the Haskell, etc. style detached function types.

@mark-i-m
Copy link
Member

mark-i-m commented Jan 9, 2017

Frankly, I think if this sort of shorthand is to be done, it should be possible to promote any expression into a closure, not just match blocks. And to my knowledge this is possible with Scala's _ notation, right?

@tupshin
Copy link

tupshin commented Jan 22, 2017

+1 to borrowing from scala on this one

@Rufflewind
Copy link

It'd be more useful if it could be used for both closures and functions and for all possible arities, e.g.:

fn foo(match) {
    (Some(n)) => n,
    (None) => 0,
}

let bar = |match| {
    (Some(n), _) => n,
    (None, k) => k,
};

println!("{}, bar(None, 42));

That way, Rust would get not just a LambdaCase clone but also a more general analogue of Haskell's pattern matching in function definitions:

foo (Just n) = n
foo Nothing  = 0

@leonardo-m
Copy link

On the other hand it's also a good idea to avoid introducing too many special cases for a minor gain in succinctness... especially if this introduces pitfalls in the language.

@dobkeratops
Copy link

dobkeratops commented Aug 11, 2017

how about fn foo(...) = match { pat1=>expr1, pat2=>expr2, ... } ... sugar for single expression functions , dropping a nesting level ( and special cased for match) The other place this might be nice is constructors, e.g. fn make_bar(..) = Bar{ ... } kill two birds with one stone

@mark-i-m
Copy link
Member

mark-i-m commented Aug 12, 2017 via email

@alercah
Copy link
Contributor

alercah commented Aug 26, 2018

Had a discussion with @Centril about this idea yesterday; I independently came up with @dobkeratops's idea.

My personal leaning here is two features:

  • Allow a function to have a body of an arbitrary expression, with = (or =>?) required as a disambiguator if it is not a block. This has plenty of precedent and I am a major fan of simplifying things, so that you can write e.g. fn square(x: i32) = x * x;. In my experience, this is huge for encouraging lots of small functions expressing e.g. predicates on types, since they become very cheap to write and take up very little space.

    EDIT: To expand a bit, I think that this has a nice benefit of basically making function definitions work similar to lambdas in that a block is optional and it's really just an arbitrary expression. Now that I think about it more, I'm leaning a bit towards => since it opens the door to if x => panic!("...") as a nice one-liner in a very uniform syntactical style, should we feel that's desirable.

  • Add syntax, either match { ... } or match _ { ... }, to automatically pull function parameters into match statements. Note that _ might be more extensible in that _ could be used to refer to "all parameters of the function" in expression context, if we wanted:

    struct Point {
        x: usize,
        y: usize,
     }
    
    impl Point {
        fn new(x: usize, y: usize) {
            if x > MAX || y > MAX { panic!("out of bounds!"); }
            Point{_}
        }
    }

@burdges
Copy link

burdges commented Aug 26, 2018

Would fn foo(..) = ..; infer the return type? Or do you mean more like

fn foo(x: X, y: Y) -> Z = Z::Initial(match x { ... }?);

@alercah
Copy link
Contributor

alercah commented Aug 26, 2018

I'm not strongly opposed to inferring the return type, but yes, I left it out. I think that, apart from sharing a goal of brevity, inferred return types are completely orthogonal as a feature.

@softprops
Copy link

Just discovered this gh issue. Big fan of this in what ever form it may take. I transitioned into rust from scala and miss this very much.

foo(|x| match x { Pat(_) => ... })

has always felt more redundant than what the analog might look like from scala ( just using the body of the match block )

foo({ Pat(_) => ... })

@1011X
Copy link

1011X commented Jan 2, 2019

Although I didn't initially agree with the original post, after writing Rust for a while I can see why this would be convenient for some. There are some parts that get pretty redundant (especially when type names are involved), and the rightward drifting of code becomes too common.

In terms of syntactic style, something like this feels like it'd fit right in:

fn unwrap(opt: Option<i32>) -> match opt {
    Some(n) => n,
    None => panic!(),
}

No redundant braces, less indenting, and it's easy to read (for me at least).

Similarly, I've written code that looks like this:

fn new() -> Struct {
    Struct {
        field: 1,
        // more fields...
    }
}

which is a very common pattern, but for something that simple I wish I could write this instead:

fn new() -> Struct {
    field: 1,
    // more fields...
}

That said, something like this would require some combination of an optional/inferred return type for functions, and allowing any expression after an fn declaration (at least in that specific case where the return type is left inferred). Inferred return types I don't think would cause any major issues. The biggest problem I can see is the initial ambiguity between the latter 2 examples, and how it'd have to be handled.

An alternative, as @alercah mentioned, would be to use = or => instead of ->, which would solve the ambiguity, but I personally like -> more.

Possible odd cases:

struct S;      // unit struct
fn new() -> S; // valid, but is useless and looks like a constructor with no code
// ambiguous integer type unless suffixed or left for compiler to choose at the end.
// ...feature?
fn nice() -> 69;

@samuela
Copy link

samuela commented Mar 16, 2019

May be worth noting that OCaml and F# also have this shorthand: https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/match-expressions.

@patientplatypus6
Copy link

Has there been any updates on this?

@rsalmei
Copy link

rsalmei commented Sep 5, 2024

Inferred return types in function signatures is a foot gun. Remember Steve Klabnick's Rust's Golden Rule.

@Rufflewind
Copy link

I think for private functions inferring return types is probably fine. For public functions I agree that having explicit signatures is important for documenting the API contract.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-lang Relevant to the language team, which will review and decide on the RFC.
Projects
None yet
Development

No branches or pull requests