rfcs icon indicating copy to clipboard operation
rfcs copied to clipboard

Shorthand for fns that immediately match

Open nikomatsakis opened this issue 9 years ago • 26 comments

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:

  • https://github.com/rust-lang/rfcs/pull/1564

nikomatsakis avatar Apr 08 '16 21:04 nikomatsakis

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.

mark-i-m avatar Aug 29 '16 14:08 mark-i-m

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

Stebalien avatar Aug 29 '16 14:08 Stebalien

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 https://github.com/rust-lang/rfcs/issues/1577#issuecomment-243143392, or mute the thread https://github.com/notifications/unsubscribe-auth/AIazwCmwL5q6s-80Au8MFe08W5CPXgbhks5qku7QgaJpZM4IDY2s .

mark-i-m avatar Aug 29 '16 14:08 mark-i-m

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 https://github.com/rust-lang/rfcs/issues/1577#issuecomment-243143392, or mute the thread https://github.com/notifications/unsubscribe-auth/AIazwCmwL5q6s-80Au8MFe08W5CPXgbhks5qku7QgaJpZM4IDY2s .

mark-i-m avatar Aug 29 '16 15:08 mark-i-m

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.

nikomatsakis avatar Aug 31 '16 16:08 nikomatsakis

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.

netvl avatar Aug 31 '16 20:08 netvl

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

mark-i-m avatar Aug 31 '16 21:08 mark-i-m

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

nikomatsakis avatar Sep 01 '16 16:09 nikomatsakis

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...

mark-i-m avatar Sep 01 '16 22:09 mark-i-m

https://github.com/rust-lang/rfcs/issues/1612 is similar as are these two forum threads: older and the newer.

mdinger avatar Jan 08 '17 04:01 mdinger

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.

burdges avatar Jan 08 '17 07:01 burdges

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?

mark-i-m avatar Jan 09 '17 03:01 mark-i-m

+1 to borrowing from scala on this one

tupshin avatar Jan 22 '17 00:01 tupshin

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

Rufflewind avatar Feb 13 '17 05:02 Rufflewind

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.

leonardo-m avatar May 30 '17 12:05 leonardo-m

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

dobkeratops avatar Aug 11 '17 20:08 dobkeratops

That doesn't really help for closures, though, right?

On Aug 11, 2017 4:08 PM, "dobkeratops" [email protected] wrote:

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

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/rust-lang/rfcs/issues/1577#issuecomment-321906801, or mute the thread https://github.com/notifications/unsubscribe-auth/AIazwLqRatfV1luNX0eRiypQmd88U1U6ks5sXLRCgaJpZM4IDY2s .

mark-i-m avatar Aug 12 '17 15:08 mark-i-m

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{_}
        }
    }
    

alercah avatar Aug 26 '18 16:08 alercah

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

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

burdges avatar Aug 26 '18 20:08 burdges

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.

alercah avatar Aug 26 '18 23:08 alercah

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(_) => ... })

softprops avatar Nov 19 '18 11:11 softprops

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;

1011X avatar Jan 02 '19 08:01 1011X

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

samuela avatar Mar 16 '19 08:03 samuela

Has there been any updates on this?

patientplatypus6 avatar Dec 24 '22 00:12 patientplatypus6

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

rsalmei avatar Sep 05 '24 17:09 rsalmei

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.

Rufflewind avatar Sep 05 '24 18:09 Rufflewind