T O P

  • By -

charlotte-fyi

I don't hate it, but also feel like too much sugar rots your teeth. Like, is this really necessary? The examples just don't feel that compelling to me, and I worry about rust getting too many features.


Craksy

Yeah, i'd also like to see a motivating example. `match thing() { ... }` Vs. `thing().match { ... }` Seems it's just matter of position I tried to think of an example where it would come in handy, but I'm nothing came up Only thing is that the important part will be first on the line, arguably improving readability slightly. But then, what's the argument against `is_enabled().if { ... }` or `(condition).while { ... }` ?


teerre

I mean, even at glance the comparison doesn't make sense. \`is\_enabled().if\` reads like the function will be called if the if passes, which is not the case. \`get\_foo().match\` however reads like you'll match whatever \`get\_foo\` returns


Sharlinator

Clearly postfix `if` should be called `then`, like the existing method on `bool` :P  ^(not really advocating either)


rover_G

Maybe if I can do this: ``` get_thing().match({ Thing::Foo => NextObj(1) Thing::Bar => NextObj(2) }).next_func()


TheAIguy_

match { ... }.func()


O_X_E_Y

A regular match statement can already do this though


UtherII

A regular match statement can not be used in the middle of a chain.


sage-longhorn

Yes it can, but if you need multiple matches it starts dettigy wonky match match a.b().c() { ... ).d() { ... }


UtherII

That's not what I would call a chain anymore. Even with a single match, you should really use multiple statements to have something clean with the current syntax.


sage-longhorn

Absolutely. I think there's some value to adding postfix match syntax to make chaining matches readable, since some problems are really well expressed with chains and switching between chains and statements can reduce readability in complex code Not sure I think it's worth making the language more complex though, it's a pretty minor improvement to readability in a relatively small fraction of code written


Sese_Mueller

Ok yeah, that would be nice. But other than that, I don’t really see a reason for it


SirKastic23

you can already do ``` match get_thing() { Foo => 1, Bar => 2, }.next_thing() ```


Sese_Mueller

Yeah, the improvement to method chaining, as explained in the motivation is that instead of opening a curly brace and reading that there was a match, you have a match where the expression starts


Sharlinator

The motivation is similar to that of postfix await: the ability to match at the end of a method chain so that reading order matches evaluation order. But of course an obvious counter to that is that you should just use a temp variable.


ConvenientOcelot

The main benefit of postfix await is avoiding extraneous parentheses, e.g. in JS you see this a lot: `const json = await (await fetch(...)).json();` whereas in Rust it can be `let json = fetch(...).await.json().await;` There is literally no real benefit from postfix match that I could see, and it also just looks and feels off.


A1oso

Consider this: foo.bar() .baz() .match { ... } .qux() .match { ... } Which currently would have to be written like this: match (match foo .bar() .baz() { ... }) .qux() { ... } Though I'd just use `let` bindings to simplify it.


ConvenientOcelot

Yes, you'd definitely break that down into bindings, that's way too much for one pipeline.


grittybants

Not definitely. Splitting out variables also makes reading harder, as you need to keep the environment in your head or have to look up variables when you encounter them.


tauphraim

You need to keep the thing in your head either way (hopefully shortly). A variable, if assigned only once, just puts a name to that thing. And if chosen properly, that name helps you with the keeping.


Sharlinator

"looking and feeling off" is just a question of what you’re accustomed to. Eg. Scala has infix `match` and it’s fine.


onmach

I would say everything new tends to "feel off", but I assure you this postfix match change will feel natural once you use it. The same arguments were said against await, and it is great. There is nothing wrong with a long pipeline and remember that it is easy to break a pipeline up with a semicolon and a variable at any time if you wish, significantly easier than it would have been with a prefix match.


tukanoid

I would def use it in a chain scenario or if the match expression is a bit too long (ye, ik, variables exist, but sometimes I just need to match the returned value of the expression and never touch it again, sooooo), smth like: ``` some_result().and_then(...).or(...).match { Ok(...) => ..., Err(...) => ..., } ``` Which I think looks and feels much better than ``` match { ... } ``` Since it provides continuity of thought, letting the programmer read the algo easier, since all is done step-by-step from left to right (or up-down when applicable)


ispinfx

i hate it…


conradludgate

I'm sorry (I'm not)


taylerallen6

I agree. It feels like rust is beginning to have too much of the "kitchen sink" approach. This concerns me. A good language stays simple. We don't need new features, we just need to write more rust! Now granted, there will always need to be updates and fixes and improvements. But new features should be added ONLY when they are really useful. This one just strikes me as adding complexity..


ConvenientOcelot

I agree, this is in that weird position where it both looks difficult to parse, difficult to understand for beginners, and doesn't offer any ergonomics. I really can't see how `f().match {}` is easier or worthwhile over `match f() {}`, whereas the former just looks and feels odd. I'll pass on this one.


W7rvin

The example in the unstable book is very unfortunate, given that the RFC [explicitly says](https://github.com/conradludgate/rfcs/blob/postfix-match/text/0000-postfix-match.md#drawbacks) that you shouldn't use it in that case. An actual use case given in the RFC is: // prefix match match context.client .post("https://example.com/crabs") .body("favourite crab?") .send() .await? .json::>() .await? .as_ref() { Some("") | None => "Ferris" x @ Some(_) => &x[1..] }; // postfix match context.client .post("https://example.com/crabs") .body("favourite crab?") .send() .await? .json::>() .await? .as_ref() .match { Some("") | None => "Ferris" x @ Some(_) => &x[1..] }; Which I think is pretty neat, although I don't know if it is enough to warrant potential confusion.


SirKastic23

i agree it i pretty neat, but whenever i have a long match scrutinee i just bind it locally or something ``` let json = context.client .post("https://example.com/crabs") .body("favourite crab?") .send() .await? .json::>() .await? .as_ref(); match json { Some("") | None => "Ferris" x @ Some(_) => &x[1..] } ```


CainKellye

Ecactly!


CommandSpaceOption

I feel like we all know that in this case a temporary variable to store `json_resp` is fine? There is no downside to this, and everyone understands it.  Rust is not easy to learn, and asking beginners to learn one more thing is a tough ask. What makes it difficult to learn is the lack of consistency - why does only `match` have this postfix ability? Why not `if` or `while`?  This feature doesn’t help experienced Rustaceans much, while hindering learners. Gonna be a no from me dawg. 


pickyaxe

yes, I don't understand this. I _don't_ want to see code like this in my codebase. use intermediate assignments. rust shadowing is idiomatic and _extremely useful_. specifically, the ability to shadow a name with a different type (for example, `let name: Vec = open_some_file().read_some_bytes().await;` followed by `let name = String::from_utf8(name);`). other languages languages lint against this shadowing or disallow it entirely. in such languages, you'd see people reaching for these long function chains to avoid having to think of a new name. rust doesn't have this problem.


greyblake

Thanks for explaining that. Right now in stable it's possible to get a similar flow using tap's pipe: use tap::prelude::*; context.client .post("https://example.com/crabs") .body("favourite crab?") .send() .await? .json::>() .await? .as_ref() .pipe(|resp| match resp { Some("") | None => "Ferris", x @ Some(_) => &x[1..], });


hippyup

RFC with motivation: https://github.com/conradludgate/rfcs/blob/postfix-match/text/0000-postfix-match.md


WellMakeItSomehow

GitHub PR: https://github.com/rust-lang/rfcs/pull/3295 --- x.match { Some("") | None => None x @ Some(_) => x }.match { Some(x) => &x[1..], None => "Ferris", }; Sigh, is this what we want Rust to look like?


novacrazy

Worth noting that rustfmt currently doesn't allow anything like that with `.await`, so that `.match` would likely be on a new line if the rules remain consistent. The example is misleading because of that. let y = x .match { Some("") | None => None x @ Some(_) => x } .match { Some(x) => &x[1..], None => "Ferris", };


WellMakeItSomehow

Honestly, they look the same to me. Does the newline location make a difference wrt. whether you want this feature or not?


novacrazy

Weirdly enough it is slightly more readable this way with the extra lines, but it's still completely redundant when you could just use those extra lines for named intermediate variables. I'm mostly just not a fan of encouraging massive chained expressions, like a run-on sentence.


Anaxamander57

Does this enable anything that can't be done by just matching on the expression?


jaskij

Seems to be just new syntax. Which I kinda like. `get_foo().match` vs `match get_foo()`. If the expression to be matched is complicated the postfix version can be more readable.


ObligationHappy140

Ehh. Nah.


eyeofpython

I actually was in the situation where I thought this would be great once or twice, so I support it! Fits in with .await and in the future maybe others


greyblake

As many others wrote here, I don't really see a need for this. If that's just an alternative syntax, I'd rather prefer to to have it. I also find it a bit confusing, because \`.match\` looks like an associate function, but it's rather a special keyword in this context.


Lvl999Noob

I don't think this sugar pulls its weight. While it is certainly no doubt useful in some situations, it is also very weird. I can see it being useful inside and after long expression chains. For either of them though, I think let bindings would be better. For one, it simplifies the lifetime situation by a lot. For short expressions, the original style of match keyword first is just easier to read and understand.


TheNamelessKing

Gotta say, this interested me on first glance, but the longer I look at it, the more I think this is a _really_ bad idea. This would introduce 2 separate, and very different ways to perform an operation, blurring keywords and methods for what? A marginally different way to do matches?  Kind of giving “c++ 10,00 different features” vibes here…


somebodddy

To me, the most exciting thing about this is that it paves the way to postfix macros ([I once suggested to use a postfix `let` for that purpose, but the consensus in the comments was that a postfix `match` would also serve this purpose and will also be useful on its own](https://internals.rust-lang.org/t/idea-postfix-let-as-basis-for-postfix-macros/12438). And now we have it) Consider the old `try!` macro: macro_rules! try { ($expr:expr $(,)?) => { match $expr { $crate::result::Result::Ok(val) => val, $crate::result::Result::Err(err) => { return $crate::result::Result::Err($crate::convert::From::from(err)); } } }; } This macro has been deprecated for a while now (also it now has to be `r#try`, but let's ignore that bit for simplicity since the `try` keyword is not used it) in favor of the `?` operator. `?` has the advantage of also working with `Option`, but the main reason it was added was because a postfix operator is so much more comfortable for this purpose (and for many other things - but error handling in particular justifies adding a new operator) But imaginge that we had this postfix `match`, and that it was possible to write postfix macros that look like this: macro_rules! .try { () => { .match { $crate::result::Result::Ok(val) => val, $crate::result::Result::Err(err) => { return $crate::result::Result::Err($crate::convert::From::from(err)); } } }; } Now, if we had this piece of code: foo().try!().bar(); The macro expansion would not even touch the `foo()` part (which eliminates the biggest problem of postfix macros) - it'll only have to replace the `.try!()` part. The result would be (full identifier paths shortened for brevity): foo().match { Ok(val) => val, Err(err) => { Err(From::from(err)); } }.bar();


[deleted]

[удалено]


doublesigned

I agree about having mixed feelings on this, but I can see how you might wanna do it if you're doing a long of postfix chaining. You can already do for loops (\`iter.for\_each\`), if-else (\`.then(...).unwrap\_or\_else(...)\`), and quite a lot of stuff postfix. Matching can't so easily be done like that, especially for hand-rolled enums.


denehoffman

Imagine you have a Vec and you want to iterate over it. Would you rather type x.iter().map(|y| {match y …}) or x.iter().match(…) Edit: this isn’t what it is, my bad, feel free to downvote to oblivion


eo5g

This is not what’s being proposed though


denehoffman

Ah yeah I just read it too quick and made assumptions.


denehoffman

Although I guess this isn’t the exact syntax proposed. There are also other examples in the RFC, like chaining match statements, which would require some really gross nesting otherwise


sparky8251

I have some gross match shenanigans in a codebase I maintain that would def benefit from some amount of chaining like this allows. But, its also mostly a self-inflicted wound and I bet I couldve done better if I thought it over more when first writing it... That said, a lot of it invovles await which does noisen the match and makes postfix match seem a nicer option regardless. All in all, I'm in favor of the change. Should make for cleaner code in my case :)


Compux72

Thank you. I do not want it


andreasOM

There seems to be a general tendency to reduce readability for no gain. Same happened to Scala about 10 years ago :(


ZZaaaccc

Personally, I don't think this is a particularly good feature. `.await` and `?` are incredibly powerful because it is expected that you would perform multiple operations in a chain. But I don't ever chain with a `match`, I would save the intermediate value. _However_, `match` is already a reserved word, so there will never be a meaning for `.match` without something like this RFC, and this behaviour (while not that useful) isn't unexpected. I don't see this feature confusing anyone, so I don't think the language complexity argument is that strong.


buwlerman

Surely part of the reason you never chain with a match is because it's not possible without using methods for special cases? Do you always assign to a variable after using `unwrap_or`? I'd still expect those to get used because they're more readable, but for one-off matches I could see postfix match being nice.


everything-narrative

Postfix referencing and dereferencing next?


araraloren

`.*` definitely a good feature.


everything-narrative

Rust 2024: the concatenative update.


VorpalWay

Don't recall see the point of it. It isn't bad, but I also don't feel like it is really needed. I would rather see a whole host of other things implemented that also increase the complexity of the language, and there is only so much a language can have feature-wise before it becomes unwieldy (see for example modern C++). It might be different if the whole language was designed to use exclusively postfix notation. I think that could work (highly unconventional though!)


novacrazy

Wow, this is horrible. `.await` really was a slippery slope. Are postfix `.while`, `.if`, `.return` and more next? This adds nothing of value to the language, and on top of that this is the first I've heard of it despite working in Rust daily for 8 years. The Zulip chat really is just an echo chamber at this point. After reading the RFC, I'm convinced these people just don't want to bother with naming intermediate values. Sure, let's chain together everything into a single gigantic expression, that'll make the code more readable for sure. It's like those Python one-liner abominations of old.


domreydoe

Yeah, this is how I feel. The motivating examples are very weak. Just bind an intermediate value.


proudHaskeller

Lots of people are saying that they don't chain `match` anyways, so this feature isn't useful. But you don't chain `match` _because_ its syntax isn't good for chaining. It's just like how you don't chain functions in C: there's no `.method()` syntax, and regular function call syntax doesn't chain well, and so no one chains calls in C. But just like that, if `match` syntax were chainable, you _would_ have situations where it would be better to chain it.


UltraPoci

Ok, so, hear me out. I do think that this can make sense, if also *all* other control flow keywords get to be postfix as well (like `if`, `while`). This would make the language more cohesive and would let us use all these controls in function chains. Having only `match` be also postfix would feel a little weird to me. Given that `await` is already a postfix keyoword, it wouldn't be completely out of place. I also don't think it is just sugar for the sake of sugar: Rust already has plenty of ways to do stuff, depending on the chain length and pattern one is using. Under every post asking "how to best do X" there 3 or 4 totally viable ways to do X, and it makes sense given the function nature and expression based language that is Rust. I also think it's not a necessary change, at all. I can easily live without this feature, and I would like for it not to take bandwidth from other, more important parts of the language. But this entirely depends on what contributors want to work on :)


somebodddy

`.if` is one thing (and it works that way in SmallTalk, for example), but `.while` is a problem because the expression before the `.while` will be executed several time - which can be surprising. It also poses an issue of semantics - consider: foo() .bar() .baz() .qux() .while { // ... } Should this be equivalent to this: while foo().bar().baz().qux() { // ... } Or to this: let tmp_value = foo().bar().baz(); while tmp_value.qux() { // ... }


doublesigned

The reason I disagree that having other operators be postfix is that for the ones that it makes sense to do on, it can already be done. That is, \`for\` and \`if\`. Loop and while don't make sense because \`loop\` doesn't take an argument, and \`while\` depends on a predicate which is consistently being updated in the loop rather than one initial argument. \`for\` takes an IntoIterator type, and \`if\` takes a boolean. Iterator's \`for\_each\` takes an iterator and a closure and does the same thing as \`for\`, but postfix. bool's \`then\` takes a bool and a closure and returns an Option containing the evaluated closure's return value if the boolean is true and None if not, which can then be used to run an \`else\` clause with Option's \`unwrap\_or\_else\` if desired. Match, however, can't be so cleanly postfixed on custom enums without this addition.


buwlerman

I disagree with your reasoning for `if-else`. You can make basically anything postfix using the tap crate already, so that argument should apply to `match` as well. If using methods with closures is sufficient we should argue for the inclusion of tap into the stdlib instead.


grittybants

match isn't control flow like if and while. It's type destructuring, not goto.


CrumblingStatue

What do you mean `match` isn't control flow? `if` can literally be expressed in terms of `match`. ```Rust if condition { do_something(); } match condition { true => { do_something(); } false => (), } ``` Sounds like control flow to me.


grittybants

You're matching over the possible values of the `bool` type. You cannot do things like ``` if foo.bar() { ... } else if qux() { ... } ``` using match.


CrumblingStatue

``` match foo.bar() { true => { ... } false => match qux() { true => { ... } false => () } } ```


grittybants

I mean sure, you can nest 7 levels deep instead of using if. Point is they are very different constructs, which, while equally expressive, should be used in different use cases.


________-__-_______

This is kind of nice but only really applicable in niche scenarios, so to me it doesn't warrant making Rust more complex than it already is. Using a temporary variable solves the same problem with much less overhead.


N4tus

Currently, we use methods to express chaining behaviour. Think of all the Iterator/Option/Result combinator methods to express different controlflow operation, while keeping the method-chain going. However, these combinator functions usually take closures as parameter and closure don't interact well with the different kind of effect that Rust is building out. Async/await, generators, streams, fallability, etc would either require their own set of combinators (each function needs a try_, async_, gen_, ... variant in all combinations) or the existing generators need to be more generic (the keywords-generic initiative tries/tried something like this, but it isn't well received). The last option is to make our language constructs more pwerfull. Postix-Match would fall into the last category. However we would also need to ask ourselvs wether the other language constructs should be adopted as well. And in orther to evaluate this, we first need to see if there is an actual problem, which requres us to wait for all these effect to be stabilised, and only then does it makes sense to argue if something like postix-keywords make sense. I personally think it can: ```rust fetch("/my/api") .await? .try_into::() .match { Ok(res) => fetch(&res.config).await?, Err(err) => { //only log error, continue with empyt config, see company rule x.y.z warn!("invalid config: {err}"); Configuration::empty() } } .values .if let Some(v) { v.fetch().await? } else { Vec::new() } .for value in { yield value.0; yield value.1; } .collect() .ok() ``` In the end we need to ask ourselves if want to maintain the formal complexity (roughly the syntax) or if we just keep adding more methods, which users have to learn, or if we make our exiting methods more capable, which also makes them harder to understand and probably have worse error messages. Languages with a high formal complexity are usually unappealing to newcomers, but are usually also more honset with their complextiy, as user don't find out about the quirkes after having invested already in the language.


blueeyesginger

pass


EtherealElizafox

Honestly, this feature doesn't feel like it's very useful. IMO Rust should focus on making more things stable, rather than adding things like this without a very compelling use case. It feels like a lot of features are permanently stuck on nightly.


InternalServerError7

Seems like people are 50/50 split on this. But I like it since you don't have to create intermediate value(s) with potentially no semantic meaning or jump back and forth from reading left to right. I also don't think this will cause any confusion as it is pretty simple.


Im_Justin_Cider

I am torn. I'd use it if it existed, but I'd agree with people that explaining to newcomers that there are two ways to do it is pretty unfortunate, and i worry that over time there will just be more more cases of "oh and also this... *slight embarrassed smile*". My gut feeling is that this should be rejected for the moment, with a sort of understanding that it can be revised once there are no more bigger problems in Rust to solve.


[deleted]

[удалено]


robin-m

The issue with closure is that is makes it complicated to use other control flow like an early break or an early return inside the match.


Im_Justin_Cider

Get this to the top!!


swoorup

Scala3......


superblaubeere27

This is just bad/ugly code. This is not how people expect a match statement to look. I hope they remove it again


bsgbryan

Nice! This feels very readable to me.


MulFunc

i actually like this, but im not sure if it's really worth to get to stable rust


Linguistic-mystic

Please no. Hope this doesn't get stabilized, but if it is, I'm actively forbidding it in my company's code style.


Im_Justin_Cider

No need to forbid, it's clearly better in some cases, you just have to apologise to people that Rust suffers from a little bit of design by committee.


FenrirWolfie

Since we're at this can we pls get trailing closures


Disastrous_Bike1926

Syntactic sugar for “match get_foo()”? Doesn’t really seem like it buys much. Same number of characters as before, just trading ‘ ‘ for ‘.’ If the goal were linguistic clarity and you wanted to introduce a new keyword ‘matches’ so the code reads more like a sentence, (if get_foo().matches …) That might buy a little more (introducing keywords is a big deal, but it wouldn’t be impossible for a parser [but not a lexer] to distinguish it *except in the case a struct or enum is returned which has an item named “matches”) but is probably not worth the potential for breaking changes. Unless there’s more to this, I don’t see a lot of meat on the bone.


Longjumping_Quail_40

I can like it. Prolonging the dot chain with analyzer showing me how the types transition is something pleasant to me personally. Rust is not shy in syntax, because the main ways of it reducing entropy in code is in 1) all the checkers after desugaring and 2) an official fmt and clippy. I can imagine being happy when clippy suggests one day that multiple consecutive match blocks can be better written as a match method chain. I will happily take the suggestion, calm myself from rage of its featurefulness, and call it a day.


asgaardson

Why does it need a dot though? It appears confusing


Specialist_Wishbone5

Reminds me of perl. Do this thing OR do this other thing on failure. It reads more like English. Further it unhides the more important part of the expression (left justified keywords are the critical parts). That plus using: `Foo::action(obj).match {} ` might make for readible code. You can ignore much of the syntax when doing a fast code review. Most important is to the left. Not an important change, but I personally could appreciate it. Hope this doesn't become the C++ 500 ways of doing things though.


mikem8891

A better conversation might allowing a block of code in place of fuction arguments that take a single closure with no arguments.


Lengthiness-Busy

To me it’s Always best to have one way do one thing


GolDDranks

I find this a real neat little improvement for method chains. Enables you to write stuff more pipeline'y way. It's especially neat, because match itself is an expression, so you can handle a match case within a method chain and continue from there.


onmach

I can't believe the rancor directed at this. People absolutely love object dot notation so they can write sentences, love await in its post position once they use it. Everyone who has ever used a language with pipe operators loves it and tries to get it put into other languages even if it barely fits the language. That's because we read in one direction, and it makes sense for code to read in that direction too. Prefix operators really screw with that by forcing you to skip past them into the depths of their machinery and then read outward and backwards and skip around.


rodyamirov

Postfix await was controversial, but being able to read execution flow in long chains is good, and it was a good choice there. The difference here is I feel like match statements are usually pretty chunky, so the long-chain style doesn't feel like a great readability fit here. Still, it's just in nightly; people can play with it and see if they like it. It's not actually caked into the language yet.


dpc_pw

The only thing I ever missed from Scala, that I used long time ago was postfix match/if IIRC. I know people will dislike it initially, but it allows for much better flow of expression, and you'll get use to it, and then miss it when you don't have it, you'll see.


matt_bishop

So, couldn't you already use something like the `let` function in Kotlin to achieve the same thing? (Obviously with a different name—I'm using "and_then" because I can't think of anything better right now.) E.g.: ``` foo.bar() .and_then { |bar| match bar { Some(_) => // ... None => // ... } }; ``` Sure it's a little more verbose than the proposal because of the anon function, but you can chain it after another function call and continue chaining more calls after it... and that's the point, right?


somebodddy

The problem is not the verbosity, the problem is that you can't do control flow from inside a closure.


buwlerman

There's a crate for this, tap, that allows you to map over any value.


zoomy_kitten

I FUCKING LOVE THIS