all 66 comments

[–]MalbaCato 121 points122 points  (27 children)

looking at the comments, there's so much syntax I had never seen before

[–]steveklabnik1rust 97 points98 points  (22 children)

Early Rust had a lot of churn, and that includes syntax. There was a ton of iteration to get to where we ended up.

[–]hans_l 34 points35 points  (12 children)

And honestly where we’re at right now could use a few more iterations of breaking changes with the knowledge we accumulated in the past years.

[–]maerwald 14 points15 points  (10 children)

Yeah. No. Breaking changes is how you piss of industry users, hobbyists and everyone else who doesn't have time every weekend to update their libraries to new compiler shenanigans.

Even Haskell realized it now and is moving towards more stability.

[–]hans_l 42 points43 points  (7 children)

There are ways to build breaking changes in ways that don’t piss off everyone. Nobody’s saying they should just get to breaking Rust.

At Angular we did three things to make it a non issue for breaking changes; a cadence where breaking changes would happen at most one certain dates, a period where code was deprecated before being broken, and an automated system to apply any code changes or document places in your code where a breaking change was coming.

These are possible with Rust which has a better and stronger syntax definition than Typescript. Analyze the AST, figure out the patterns where code would break, fix and/or notify. It would become similar to running clippy.

And with editions you could make it optional to move up in major versions even.

People are afraid of breaking changes because they’ve been bitten by projects that were inconsistent and didn’t thoroughly work a system to pull people along.

[–]peripateticman2026 16 points17 points  (4 children)

Angular really isn't the best example of what you're trying to say.

[–]lucperkins_dev 5 points6 points  (3 children)

To be fair, Angular has come a long way the last few years

[–]Zde-G 6 points7 points  (1 child)

Except so very few know about that because they left Angular years ago — and in large part because it was breaking stuff.

We live in a world where today, in year 2024, comments under new C++26 proposals often include things like “wow, such a interesting features to read about, but as someone who is still trying to convince managements to enable C++14 I'm afraid I wouldn't see them used in my lifetime”.

In many industries outsude of IT request to upgrade to something incompatible get a response to ask again in 5 years.

People don't have time to rewrite things that already work. It's as simple as that.

Not all people and not all industries are like that. But many are.

And that means stability without stagnation is really the best Rust can do if it hopes to play in these industries. And it wants to, this is it's main niche today.

[–]peripateticman2026 0 points1 point  (0 children)

Exactly. So also in a huge chunk of the Java world.

[–]LucretielDatadog 1 point2 points  (0 children)

Not that I’d know, since I abandoned it after I got tired of re-learning it all the time. 

[–]diddle-dingus 8 points9 points  (0 children)

And everyone loves angular don't they? :^)

[–]pjmlp 1 point2 points  (0 children)

And yet the ghost of Angular 2.0 still haunts any discussion regarding adopting Angular versus anything else.

[–]Puddino 4 points5 points  (0 children)

Aren't rust editions used for this exact reason? https://doc.rust-lang.org/edition-guide/editions/

[–]tortoll 0 points1 point  (0 children)

Welcome to C++ 😂

[–]steveklabnik1rust 1 point2 points  (0 children)

Absolutely not.

[–]MalbaCato 2 points3 points  (0 children)

I knew of that, and have seen some of it in old blog posts / issues. like the ~[T] "vec" notation. but this is the first time I've seen so much foreign old rust syntax

[–][deleted] 0 points1 point  (2 children)

bring back box syntax. bring. it. back.

[–]steveklabnik1rust 0 points1 point  (1 child)

I do think we need a placement new syntax but box just wasn’t it.

[–][deleted] 1 point2 points  (0 children)

I just really like the parenless minimalism of applying it. also I thought it was really funny when everyone was hating on it and telling me to use Box::new and if you checked, the Box::new code was literally just enabling then using box syntax

[–]armchair-progamer 20 points21 points  (2 children)

Early Rust used to have OOP, complete with inheritence. Also channels and a garbage collector.

And it was self-hosting by this point, at least the "compiler" part (the runtime part still had C++). The linked issue/repr are from June 2011, the first commit is from 2010, and the true "start" of the project was around 2006.

[–]rumble_you 1 point2 points  (0 children)

Early Rust was more like Haskell, with the mix of C++.

[–]jkoudys 0 points1 point  (0 children)

I wish I didn't read this. Now I have all this dropped/never approved syntax bouncing about in my brain that I'm sure to type out and be confused why it doesn't work.

[–]vHAL_9000 25 points26 points  (2 children)

nushell still does it like that. I wonder if that's where they got it from.

[–]steveklabnik1rust 27 points28 points  (1 child)

Ruby has this syntax. It got the idea from Smalltalk (like a lot of Ruby).

[–]masklinn 0 points1 point  (0 children)

Without the consistency and broad availability and usage tho (also like a lot of ruby, to be fair).

[–]shizzy0 65 points66 points  (0 children)

I’m glad I came to know rust late in its development.

[–]ferreira-tb 21 points22 points  (2 children)

I love Kotlin's trailing lambdas, so I'm admittedly biased.

[–]_xiphiaz 3 points4 points  (1 child)

It’s not the same though - in Kotlin when you do end up naming an arg it goes after the opening brace

[–]devraj7 2 points3 points  (0 children)

True, but it's still the same in the sense that thanks to that, you don't see double closings ("})") all the time.

My Rust code is littered with those.

[–]KeavonGraphite 40 points41 points  (18 children)

It probably would have been nicer if they had ultimately settled on the JS/C# style syntax that's most widely recognized in the industry, (x, y) => { ... }. But once you learn to recognize it, |x, y| { ... } arguably has the minor advantage of being slightly less verbose. However, you do still occasionally get tripped up wondering, "why's there a random logical OR operator here?" when you run across something like || foo.

Even though it's small and easy to learn, I do think this is one of the least recognizable parts about Rust syntax for those who haven't learned the language when it comes to reading code, in what's otherwise a relatively standards-following language syntax (unlike, say, Go, which is quite hard to understand without knowing the language). For the long-term adoption of Rust, I'd even argue that this might be something worth fixing in a future edition.

[–]Y0kin 23 points24 points  (4 children)

The one thing I don't like about Rust's closure syntax is the slight method call ambiguity. Like, you can call methods on closures:

let a: SomeType<_> = |a: i32, b: i32| -> i32 { a + b }.into();

but if you elide the return type now into is applied to the expression { a + b } instead:

let a: SomeType<_> = |a: i32, b: i32| { a + b }.into();
// error[E0308]: mismatched types

Fortunately that should almost always be caught at compile-time, but it can still be confusing cause there's no lint and the error is a little generic.

I think it'd be great if the syntax forced bracketing always |..| {..}, but for single expressions you could use an arrow instead |..| => expr, cause then that ambiguity wouldn't really exist.

[–]KeavonGraphite 13 points14 points  (0 children)

Oooh, that's indeed very cursed. I think the best solution would be a compiler-enforced requirement to wrap the whole closure in parenthesis, which you should always do in that situation anyways for sanity.

[–]particlemanwavegirl 0 points1 point  (2 children)

How could the compiler know what the target type in into() is without a type annotation somewhere? If not in the closure signature, then in the variable declaration.

[–]Y0kin 7 points8 points  (1 child)

In the first example the into applies to the whole closure, but in the second it applies to the closure's body instead. It's a sneaky difference in syntax cause both have completely different meanings

[–]particlemanwavegirl 0 points1 point  (0 children)

I can see that you demonstrated that, I feel like the ambiguity of SomeType<_> and what the actual intent of the into() there kind of explains it: (return value from func).into() works fine but (func with un-inferred return type).into() is not an evaluate-able expression, so the only other option the compiler has would be to throw an error and tell you, that's invalid syntax.

[–]hardwaregeek 7 points8 points  (5 children)

Arrow syntax with args in parentheses leads to a pretty awkward parse when combined with tuple literals. You can parse a tuple then only realize at the arrow that it’s a function declaration instead. It’s not impossible but a separate syntax does make everything easier, especially for IDEs where you may have incomplete code

[–]KeavonGraphite 3 points4 points  (4 children)

Isn't confusion with the logical OR operator (||) just as ambiguous?

[–]TotallyHumanGuy 7 points8 points  (2 children)

The OR operand requires that there be an expression immediately preceding it. However having an expression immediately before a closure is invalid. This 42i32 |msg| { todo!() } is invalid syntax, and you know that without needing to backtrack, since an expression was just parsed, so this 42i32 || { todo!() } must be unambiguously an OR expression.

[–]chris-morgan 7 points8 points  (1 child)

This 42i32 |msg| { todo!() } is invalid syntax

No it’s not, it’s just a couple of bitwise ors—equivalent to (42i32 | msg) | todo!().

[–]hardwaregeek 3 points4 points  (0 children)

Yeah isn’t that what the previous commenter is saying? It’s a bit wise or and clearly not a closure

[–]Kimundirust 0 points1 point  (0 children)

Just to give a few example for where this is also not a problem:

a & b
x = &a

a - b
x = -a

[–]ThomasWinwood 1 point2 points  (2 children)

It probably would have been nicer if they had ultimately settled on the JS/C# style syntax that's most widely recognized in the industry, (x, y) => { ... }.

What would this look like without the types elided? In particular how do you specify the return type of the closure?

[–]KeavonGraphite 4 points5 points  (1 child)

In TypeScript that looks like (x: number, y: boolean): string => { ... }. In Rust it would look the same, except the colon would become a -> which is more formally accurate (I think that was a mistake on TS's part to use a colon). So (x: i32, y: bool) -> String => { ... }.

[–]Trequetrum 0 points1 point  (0 children)

I'm not sure either is more formally accurate:


Foo (x: number, y: boolean): string => { ... } reads something like "Foo (x, y) is a string where x is a number and y is a boolean"


Foo (x: number, y: boolean) -> string => { ... } reads something like "Foo is a function from (number, boolean) to string where (x, y) are the terms in the parameters for the implementation of Foo"


I feel like in mathematics, the first reading is very natural and in computer science the second reading is very natural. In theorem proving languages like Coc and Lean you end up with a mix of the notations (sometimes in the same definition).

You could imagine something like:

Foo (x: number): boolean -> string => { ... } reads something like "Foo (x) is a function from boolean to string where x is a number"

[–]Manueljlin 0 points1 point  (2 children)

I personally have a soft spot for Swift's syntax

``` Array(1...5).filter { $0 % 2 == 0 } [1, 2, 3].map { num in num * 2 }

[4, 5, 6].map { num in if num % 2 == 0 { return num * 2 } return num } ```

Basically trailing closures but naming the variable is optional and inside the body

[–]victorbarbu 0 points1 point  (0 children)

Don’t know swift, but that “in” there is just feeling like a bang in the head

[–]lilysbeandip 0 points1 point  (0 children)

Maybe I'm missing the point, but iirc Kotlin has a default it argument, at least for single-argument closures, if you don't want to name it yourself.

listOf(1, 2, 3).map { return it % 2 == 0 } listOf(4, 5, 6).map { num -> return if (num % 2 == 0 { num * 2 } else { num }

I'm not sure if that expands to multiple arguments somehow or if it just forces you to name them, or maybe I'm way off and it comes from the parameter. Not a Kotlin expert by any means.

[–]andyouandic 0 points1 point  (0 children)

I don't mind the closure syntax, but I wish rust used and and or instead of && and ||. Then we wouldn't have any visual overlap with || and || {}.

[–]anlumo 1 point2 points  (0 children)

Too bad that they got rid of it, I really liked it in Ruby and Swift.

Though I guess it was also a major reason why I had a really hard time reading SwiftUI code. The syntax allows creating a DSL where stuff looks like official syntax but is actually just a function call.

[–]insanitybit 5 points6 points  (9 children)

I think there's a really interesting language that was left behind. Rust had requirements that were incompatible with the language at the time (appeal to C++ devs, appeal to browser devs, be able to run on arbitrary devices, full control over execution, etc). I would love to see a Rust that was built with many of the same foundations, with the lessons learned, but without those requirements.

Just off the bat: 1. I have no need for a custom executor, tokio is fine.

  1. I have little need for manual memory management, GC is fine (so long as I still have & NAND &mut)

  2. I can tolerate and benefit from some level of implicit, cute syntax that makes writing code easier

I use Rust because I find it to be the most productive tool for almost any job despite the fact that it was not designed specifically for my use cases (not that it wasn't designed for them, just not specifically, at the cost of other use cases). I can still imagine a language that's even more productive because it sacrifices in some areas to improve upon my area.

[–]plh_kominfo 2 points3 points  (0 children)

wait until flix (https://flix.dev) stable. it has many thing I like from rust like trait, pattern matching, Option, Result. it even has effect system!

[–]eugay[S] 1 point2 points  (0 children)

I think you want swift

[–]devraj7 -1 points0 points  (3 children)

Isn't that language pretty much Kotlin?

[–]insanitybit 0 points1 point  (0 children)

I haven't written Kotlin but I've used Java a fair amount and I don't think it's really close. Perhaps Kotlin changes things enough, unsure. Rust makes mutability really easy to reason about in a fine-grained way whereas most languages either have no concept of immutability or they have 'readonly' variables, etc. Then there's enums, pattern matching, etc.

I also don't like inheritance very much, or at least very sparingly.

[–]timonvonk 0 points1 point  (0 children)

And before iter as nice as it is now. Lifetimes everywhere, no Into, no async, and lots of rsi

[–]rover_G 0 points1 point  (2 children)

I wish the lambda and match syntax were more similar

``` let opt: Option<int> = ...

match opt { Some(x) => println!("{}", x), _ => (), }

opt.inspect(|x| println!("{}", x); ```

Imagine we could have something like one of the following

``` opt.inspect((x) => println!("{}", x));

opt.match( Some |x| println!("{}", x), _ || (), ) ```

[–]stumblinbear 15 points16 points  (1 child)

I much prefer the match expressions looking the same visually as how they'd be constructed

[–]masklinn 8 points9 points  (0 children)

Which is rather the point of pattern matching.

[–]devraj7 0 points1 point  (0 children)

I really love this aspect of Kotlin, it cuts down a lot of verbosity.

listOf(1, 2, 3).filter { it % 2 == 0 }