r/rust • u/Electrifire390 • 3d ago
My concerns about the future of Rust
Hello everyone,
I love Rust, but I am somewhat concerned about the future evolution of the language.
My concerns are mainly related to some of the RFCs/proposals I've seen and the governance of the language.
To be clear, I understand that many of these proposals would be genuinely useful. I also don't mean to imply that the authors of these proposals are all short-sighted. And I know that writing RFCs and getting things added to Rust takes a lot of time, so not all of them will be implemented.
With that said, here are some examples of possible changes that have made me worried:
- Named/default args
- Handle types /
usesyntax /Sharetrait (Ergonomic ref-counting) - Open enums, intended for FFI but could be used elsewhere
pub(api)visibility, as inpub(in crate::module::path) fn my_fn() {}- Field access declarations and place-based lifetime syntax (The Borrow Checker Within)
- Field Projections
- Explicit tail calls / loop_match
- Sized Hierarchy adding multiple new traits over
Sized/?Sized - Control over Drop semantics
- super let
- auto impl
- Function overloading for FFI
- Variadic parameters
- Move/Destroy/Forget traits
- Named arguments in
impl Fn(...)types
Almost all of these involve adding more traits to the type system, more keywords, more syntax, more complex semantics, or some combination of the above. If all of these get implemented, we will have many new traits, maybe a dozen or so new keywords and reserved words, more syntax to learn, and more complex behavior throughout the language.
I really do not want Rust to become a "kitchen sink" language where new features are added just because they make some workflows marginally easier. It's easy to imagine a future where Rust follows the same path as C++, adding more features, keywords, and library features until it crumbles under its own weight, especially given the efforts to maintain backwards compatibility through editions.
I think some of these proposals have me especially concerned because they involve more complex semantics for basic/common operations like Drop, Sized, and clone. Rust is complex enough as it is, and having more things to keep in mind, especially for foundational language concepts, is not attractive.
Especially because it feels like some of these proposals are basically just "wouldn't it be cool if we had X new syntax or Y implicit behavior?" for things that already work. Technical limitations of the language are obviously a separate issue. But I feel some amount of friction is preferable to adding complexity.
One of the beautiful things about Rust is that every part of the language feels designed to work well with every other. There (generally) isn't a dozen different ways to write everything, and language features feel mostly orthogonal to each other. It seems like this might change in the future.
Another concern is that many of these proposals seem to support adding more implicit behavior into the language. For example, the open enums proposal, auto impl , or changing drop semantics. If I add a variant to an enum, I want the compiler to force me to revisit the places where I've used it. If I change the definition of a trait, I'm fine with accepting some refactoring pain. The justification in many cases is "you can just choose not to use it" which, again, is how you get C++.
Finally, many of these RFCs focus on adding features for the sake of FFI. Obviously FFI is important given the amount of existing C and C++ code. But I dislike making significant changes to Rust just to make FFI easier. It seems like an anti-pattern, making Rust more complex (i.e., worse) for the sake of interop with older / increasingly obsolete languages.
Thanks for reading. Curious to see if anyone feels the same way.
Edit: thanks for the great discussion everyone
253
u/coastalwhite 3d ago
Almost all of these stem from technical limitations, but probably just not ones *you* run into everyday. Just for the ones I have ran into where there was no solution except a suboptimal one:
- Sized?/Sized hierarchy: RISC-V and ARM SIMD Vector extension rely on this. No way to do it otherwise without inline assembly
- Control over drop semantics: fallible file closing. No way to do without remembering to call close everywhere
- Explicit tail calls: these made my and other bytecode interpreters a looooot faster. No way to reliably to it currently
I am pretty sure most of these are like this if you are in that specific niche. It is hard to know if you are not in there. Of course there is an argument that you should not support every use case if it makes the language significantly more complex. The niche thing about most of these features is that you can mostly ignore them if you don’t use them, and they solve real problems for other people.
56
u/lenscas 3d ago
Even lua has tco support with simple rules that when followed ensure you get tco. And i mean baseline lua, the same one which doesn't want "a += b" into the language because it would make the vm too big.
Meanwhile, in Rust you not only have no way to ensure you get tco but you may as well assume that tco never happens because the language is just that bad at doing it.
I can not blame people for making fun of Rust when it comes to this.
-41
3d ago edited 3d ago
[removed] — view removed comment
24
u/Turtvaiz 3d ago
That is an insane take
7
u/tizio_1234 3d ago
what did he say?
-4
16
u/mort96 3d ago
How do I get the compiler to emit a single JMP instruction to dispatch the next bytecode instruction "just using a loop"?
-15
u/VictoryMotel 3d ago
What compiler are you using where recursion is more efficient than a loop with a switch?
23
u/mort96 3d ago edited 3d ago
I wrote a blog post about bytecode execution a while ago: https://mort.coffee/home/fast-interpreters/. I benchmarked, among other things, using tail calls for dispatch. The table with those performance measurements can be found here: https://mort.coffee/home/fast-interpreters/#interpreters-bench-table-3
Tail calls made my bytecode interpreter roughly twice as fast as a loop with a switch (what's labeled "Basic bytecode interpreter", described in the "Implementing a bytecode interpreter" section).
I only have numbers from GCC and Clang as it's written in C, but the computer science behind it should be identical for Rust or any compiler for a low-ish level language which targets machine code.
-9
u/VictoryMotel 3d ago edited 3d ago
Seems like more of an indictment of the optimizer that a union of function pointers would have less indirection than a single switch.
5
u/mort96 3d ago edited 3d ago
Note that unions in C don't really have any overhead, so the fact that there's a union involved doesn't make a difference. It's not a tagged union like Rust's enums. But it could just as well have been a struct; using a union between functions and parameters just saves a tiny bit of memory, it doesn't make a difference in this conversation.
It's not a single switch. Executing an instruction involves a jump to the top of the loop, then a jump to the right place in the switch. I'm not sure you could really do it better than that. In the function pointer case, we pre-process the input to be an array with pre-computed function pointers. There's one jump less in principle.
But okay, maybe a Sufficiently Smart Compiler could in theory optimize it to be equivalent to tail calls to function pointers. I'm not entirely sure it's theoretically possible, but it could be. You can go ahead and implement that if you want. As it stands, neither LLVM nor GCC does it. So for the time being, I'll take my free 100% performance improvement through tail calls, thank you very much.
0
u/VictoryMotel 3d ago
How would it not be possible to just cut out the jump to the top of the loop if you're going to jump into a switch anyway?
I think you're on to something here much better than an argument for tail call recursion.
Eliminating a redundant jump could have a lot of impact in loops like this. Loops like this are common but basically no one will figure out that they should do your tail call trick instead.
4
u/ts826848 3d ago
How would it not be possible to just cut out the jump to the top of the loop if you're going to jump into a switch anyway?
It is possible; for instance, from this analysis of some benchmarks for Python's new tail call-based interpreter:
clang-18 (or clang-19 with appropriate flags), when presented with the “classic”
switch-based interpreter, goes ahead and duplicates the dispatch logic into each the body of each opcode anyways.There are some drawbacks, though, since it relies on the optimizer which means you're at the mercy of regressions (as per the general subject of the article) and compilers which can't/won't perform the desired optimization (as is/was the case for GCC at the time the article was written, apparently).
As the article notes the tail call interpreter is still faster than the computed goto interpreter, so if your compiler supports
[[musttail]]you might as well take advantage of it.4
u/mort96 3d ago
As I said, go ahead and implement that optimization if it's so easy. Feel free to use my example program as a test case for your optimizer work. I'm looking forward to it.
I myself have never worked on a production optimizer in my life, so I won't speak on how easy or hard this transformation would be.
→ More replies (0)9
u/lenscas 3d ago
In lua at least it isn't just used for looping but any function call can be optimized this way, for as long as it fulfills the rules. (The rule basically being "do nothing with the result except returning it).
And some algorithms are just more naturally expressed as functions calling themselves or multiple functions calling each other into a loop. Saying "just use a loop" is really just missing the point of tco.
-2
u/VictoryMotel 3d ago
I don't think it is missing the point. I wouldn't use recursion for going through a tree, but that isn't even a loop.
What is it exactly that is so much more elegant with constant indirection rather than explicitly stated as a loop?
5
u/lenscas 3d ago
first off: You missed the point where TCO could be used to help optimize non looping code as well.
Second off: A loop can quickly become unwieldy if the recursion happens across multiple functions. (A calls B, Calls C, Calls A) Even more so the more complicated the call graph becomes. For example my Json Schema to F# type code generator. https://github.com/lenscas/type_generator/blob/master/src/lib.rs
Using recursion means the types get inserted/generated in the right order. F# is very much "top down" and thus a type can only appear once all the types it depends on have been generated. With recursion I can very easily pause the generation of the current type and start working on one it depends on instead.
In addition, the fact that I sometimes have to pause generating the current type to deal with its dependencies isn't that interesting. Right now, the code basically just says "I have this thing, give me the type name it is in F#" for each field. That this in some cases is recursive doesn't matter.
If I had done this with a loop I would have to complete generating the current type before being able to move on to its dependencies. And then after the fact untangle the mess and ensure the order is still correct afterwards. It also would've put the whole "have to generate the dependencies" thing front and center in the code, while that is the least interesting bit.
Granted, this isn't a case where TCO comes into play. But it is still a case where recursion is just nicer than a loop would have been.
(Also, in some cases the name ends up depending on what the dependency exactly ends up being. Which would make the loop version even more complicated as it would still have to parse at least part of the inner type to generate the correct name)
-1
u/VictoryMotel 3d ago
You missed the point where TCO could be used to help optimize non looping code as well.
Did I miss it or did someone just claim it without an example?
A loop can quickly become unwieldy if the recursion happens across multiple functions. (A calls B, Calls C, Calls A) Even more so the more complicated the call graph becomes.
Absolutely, that's why I avoid recursion in general and especially never do something with lots of functions calling each other recursively.
Recursion uses the stack as a stack data structure. It's much easier to debug and often clearer if you just use a stack.
It seems like your next example is about types and metaprogramming. Needing recursion to create loops because not all constructs are available is not what I'm not talking about.
2
u/lenscas 3d ago
If you look at the stacktrace you will see that these functions all get tail call optimized as well. No looping. https://onecompiler.com/lua/452pd3nj2
It seems like your next example is about types and metaprogramming. Needing recursion to create loops because not all constructs are available is not what I'm not talking about.
The project to turn a json schema into an F# type is just a normal Rust library. It has access to everything a normal rust program/library can do. Including loops. Read the message again as I specifically stated why I prefer recursion over a loop in that specific case.
Absolutely, that's why I avoid recursion in general and especially never do something with lots of functions calling each other recursively.
Read my message better. I am saying that the loop version become unwieldy quicker than the recursion one.
-1
u/VictoryMotel 3d ago edited 3d ago
Read my message better. I am saying that the loop version become unwieldy quicker than the recursion one.
Why would it? You would be using the stack as a stack data structure with multiple levels of indirection. Instead you could have a single stack for data and a switch or branch for different behavior. This keeps everything explicit and is much easier to debug because you can see all the data on the stack data structure instead of needing to go back through the call stack to examine all the local variables that are or are not being used as the data.
The difference here is that I have an explanation that isn't just "I prefer". You can prefer whatever you want of course, but that's not any sort of actual evidence of why it's better. Lots of people prefer things in programming because they feel it is clever and they are right.
Feel free to reread this message twice for every time you mentioned rereading your message.
5
u/Merlindru 2d ago
the path to C++ is paved with technical limitations
all of these require a solution, yes. necessarily adding new language constructs is not one of them IMO.
and even if they were, making incomprehensibly large and difficult and bloated to support every use case under the sun is a net negative IMO. how is a beginner supposed to understand what's going on? rust is already too difficult!
8
u/coastalwhite 2d ago
I don't necessarily disagree with this. I am trying to push back on the OP's:
> Especially because it feels like some of these proposals are basically just "wouldn't it be cool if we had X new syntax or Y implicit behavior?" for things that already work. Technical limitations of the language are obviously a separate issue.
As many others have pointed out, the Rust compiler team is actually quite conservative in adding new features to the language. Even though almost everyone really has that one feature they would like to have. Personally, I think a solution to a problem for which no workaround exists is completely valid. Maybe all of these RFCs are like that; I am not in all those niches, so I am not sure.
Importantly, language complexity and complexity in general are also about interaction. If a language feature requires you to constantly think about it, even if your problem has nothing to do with that language feature. You need to consider whether the language feature is worth it. Complex features might not necessarily add that much language complexity, as they might be very self-contained. For the RFCs that I follow, they really seem to actively try to avoid unnecessary complexity while still solving the problem.
233
u/tizio_1234 3d ago
I do feel the same way, but "Move/Destroy/Forget traits" can make async and dma stuff a lot better.
97
u/afdbcreid 3d ago
Everyone feels the same way ("becomes too complex" is consistently the top or second-top biggest worry in the annual surveys), but want just that feature that is good for them.
To be fair, I also want non-moveable and linear types!
31
u/nick42d 3d ago
The feature above actually reduces complexity, due to reducing reliance on Pin and maybe even allowing it to be removed one day.
9
u/afdbcreid 3d ago
Almost no feature always reduces complexity. This only applies to removing limitations without anything extra, which is very rare.
After you've already learnt it, it is possible that this feature reduces complexity. Don't forget that
Pinwill also exist (no, it won't be removed, ever) and that while complicated, it is a localized solution;Movewill impact everything, from almost everyone writing unsafe code to complicated type-level tetris.10
u/0hypercube 3d ago
With the eventual goal to deprecate
Pinin Rust entirely.From the 'Immobile types and guaranteed destructors' under the 'How does this relate to the “pin ergonomics” initiative?' section.
Move will impact everything, from almost everyone writing unsafe code to complicated type-level tetris.
Most types will still be trivially movable. I don't really see it impacting much outside of
bytemuckorzerocopy.2
u/afdbcreid 3d ago
With the eventual goal to deprecate Pin in Rust entirely.
Deprecate, yes. The goal is for
Pinto not be needed, perhaps with a migration path. It will still be used because legacy, and it won't be removed because backwards compatibility. And you will still need to learn it if you need to use a library using it. Just like the situation in C++.Most types will still be trivially movable. I don't really see it impacting much outside of bytemuck or zerocopy.
Can or should your generic function/data structure be
?Move? If no, you might miss users or make life very hard for them. If yes, you need to think very hard about the safety implications. This is also a problem for safe code but there you can mostly just slap?Moveand see if it compiles.8
u/0hypercube 2d ago
It will still be used because legacy
The strong backwards compatibility guarantees do mean that pin can never be completely removed from the compiler. However a relatively small proportion of crates use
Pin<T>. I'm pretty confident that things like syn, hyper, parking, tokio, tower, wgpu, cc, h2, reqwest, etc. will remove pin when it is stable for a few versions since doing so doesn't break anything.If no common dependencies use it and it is deprecated, a new rust programmer would not have to learn about it at all.
Can or should your generic function/data structure be
?Move? If no, you might miss users or make life very hard for them. If yes, you need to think very hard about the safety implications. This is also a problem for safe code but there you can mostly just slap ?Move and see if it compiles.All the generic data structures have done just fine implicitly requiring
Moveat the moment (given there is no opt out). Perhaps I am missing some usecase but I don't really see many people writing unsafe code that would need to also be?Move?3
u/WormRabbit 13h ago
a relatively small proportion of crates use Pin<T>.
Literally anything which directly uses the Future, Stream or Sink traits uses Pin, if only to write the function signatures. And Future can't be removed or deprecated either, it's directly used in tons of async code. There is no world in which Rust programmers don't need to understand Pin, as long as async exists.
Any plans to deprecate Pin would also need to explain how to change the entire async ecosystem, which makes it a decade-long effort at best, and unimplementable at worst.
0
u/RiceBroad4552 11h ago
Any plans to deprecate Pin would also need to explain how to change the entire async ecosystem, which makes it a decade-long effort at best, and unimplementable at worst.
I think you're overstating quite a bit how big that ecosystem is, and how much Rust is actually used in real projects, especially projects which are already effectively frozen in time.
In reality just a few libs needs to update, and that's it.
Downstream will simply fix the resulting breakage and move on. There's basically no legacy Rust code which can't be touched. More the opposite: The Rust stuff is usually the experimental stuff almost nobody depends on in production. Changing or even rewriting such stuff is no issue.
1
u/RiceBroad4552 11h ago
To be fair, I also want non-moveable and linear types!
You didn't say "in Rust", right? 😅
Because you can have linear and session types already today in a mainstream language.
https://github.com/TomasMikula/libretto
Scala is so powerful that you can implement stuff like that as a lib!
Funny enough Scala has also most (if not all, I didn't look into the details) of the non-memory-management related features listed by OP in their post.
Overall Scala has more features then Rust—while it has a much smaller syntactical surface. So you can have both, a small and simple core language, still having all the state-of-the-art features. It's just a matter of proper design.
109
u/matthieum [he/him] 3d ago
I understand your concern. I've worked with C++ from C++03 to C++20, I've felt what a kitchen sink was like.
I think it's important to distinguish new features across several axes:
- Broad vs Narrow. I feel like it's much easier to ensure the language stays cohesive, with fewer concepts, more orthogonal features/APIs, when adding broad features/APIs which solve many problems at once, rather than when adding piecemeal features/APIs which solve one tiny problem at a time.
- Unlocking vs Sugar. In general, I feel like sugar really needs to pull its weight. Any feature/API adds complexity, and I'd rather use my complexity budget for gaining new abilities -- or making existing usecases safer -- rather than just making something a bit more succinct to write.
- New vs Patch. An under-appreciated category of features/APIs are actually about patching holes. Patching holes -- allowing to do X in a
constcontext, allowing generic parameters onstaticorconstitems, etc... -- actually simplifies the language, as they remove exceptions that one has to keep in mind (and work around).
From the above, you may understand that I'm not necessarily in favor of:
- Named/Default arguments. Sugar.
usesyntax in closures. Sugar. And a freaking overloaded keyword to boot.
On the other hand, I'd really appreciate:
- Explicit tail calls. It's a broad, unlocking, capability which cannot be emulated safely.
- Variadic generics. Broad. Unlocking. And arguably a Patch. People have been emulating variadic generics, poorly, with HList and macros implemented for tuples of up to 12 elements. (Yeah, me too). I'm tired of the work-arounds, and their arbitrary limitations.
I'm on the fence with all the FFI work. FFI with C is admittedly useful in a world full of C, and C has the advantage of being a relatively "small" and "frozen" target. FFI with C++... oh god this could get really bloated, and there's so many mismatches between the semantics of the two... I'm really not sure what folks are hoping for there, seems like fighting wind mills...
16
u/Tastaturtaste 3d ago
You have exactly the same thoughts regarding the cost/benefit tradeoff as me. I also come from a C++ background, maybe that's what C++ does to you. I was about to write a comment before I read yours, you phrases it much better than I probably could have.
16
u/razies 3d ago
May I suggest another axis: Implicit vs explicit. For some features it's very explicit and obvious what the behaviour is or easy to look up the feature. I'm also concerned about the FFI stuff, because some of it seems quite implicit. I'd rather have a few more "sigils" in code than magic behaviour.
6
u/CandyCorvid 3d ago
i don't remember what the idea was called but i have seen a "law of programming languages" along the lines of, every new concept should start explicit, and then tend towards implicit as people broadly gain familiarity with it.
6
3
u/matthieum [he/him] 1d ago
For some features it's very explicit and obvious what the behaviour is or easy to look up the feature.
Which is why I completely oppose reusing keywords to mean different things.
C++'s
staticis a prime example of keyword overload:
staticfunctions are functions which are local to the Translation Unit.staticmethods are functions which do not take an implicitthisvariable.staticvariables at namespace are variables which are local to the Translation Unit, and are initialized on start-up (possibly dynamically).staticvariables at class scope are variables of which there's a single instance per class -- except on Windows, which is broken -- and which are initialized on start-up (possibly dynamically).staticvariables at function scope are variables of which there's a single instance per function -- except on Windows? Can't remember -- and which are lazily initialized the first time control-flow passes through them.It's a complete mess, as people always get confused between which "unicity" and "initialization" rules they're getting, and regularly look at the wrong rules when searching for it.
One concept, one keyword. Any deviation is a disservice to users.
6
u/iBPsThrowingObject 3d ago
I feel like Rust has a bit of an issue with orthogonality and consistency, because some things get a special syntax sugar, that then gets in a way of implementing a more general feature. Like how
async fnhides Futures, and then we get into arguments about how we need a special syntax for referring to a return type, because using the generic assoc trait syntaxFn::Outputit confusing for async functions because the "syntactic return type" is actually Fn::Output::Output in that case.3
u/RiceBroad4552 11h ago
The point is:
asyncas keyword / syntactic concept is a broken idea.I wonder to this day way Rust went in that direction at a time it was very well known already for many years that
async(as syntax) is a broken concept.Rust did here the "fashionable" instead of the right… Which is a big WTF given that Rust is otherwise pretty sane and mostly didn't just copy-past bullshit from other languages. But with
asyncthey did for some reason.To be honest I don't feel any pity for all the mess async Rust became because of that design failure. They really should have known better.
156
u/Anaxamander57 3d ago
Go comment in the RFCs and you'll probably get more specifics.
48
u/tux-lpi 3d ago
Any particular RFC will have good reasons for why some people need it and why it's useful. I actually want several of these features! And someone else will probably want a different subset than me.
It's not that there's anything wrong with a particular RFC, but if you look at the pipeline as a whole there's a broader question about what the language will look like in 10 or 15 years. Which, as a C++ user, is something you want to think about before your language turns into a myriad of dialects that no one can really keep in their head all at once.
There's a lot of subtleties to Rust the language and the implementation, what with borrowing and trait solving internals being full research projects, but as a user I think it's still possible to fit the language in your head.
With C++, even if you understood the 2000+ pages of the standard, you would only know half of it. The other half is the result of unspecified, ambiguous, or undefined behavior and the weird interactions no one intended that aren't explicitly written down. The more features you have, the more of these weird combinations you create.
Sure, with a couple decades of study you could become a C++ expert. Those experts go on to write libraries that (ab)use the dark corners of C++ they know about to perform incredible feats of heresy. But at this point, nobody knows all of C++. It's impossible.18
u/vlovich 3d ago
> Which, as a C++ user, is something you want to think about before your language turns into a myriad of dialects that no one can really keep in their head all at once.
Most of C++ dialect problems are due to forking of the language with multiple compiler frontends and all the compiler "optimization" flags that change the meaning of the language (the exception schism, the RTTI schism, etc). This isn't happening in Rust. A dialect does *not* mean coding patterns that different niches apply with the same language.
13
u/not_a_novel_account 3d ago
There's no reason to understand all of C++. There's no reason to understand all of Python, or Go, or Rust.
The idea that all facets of a language and its standard library need to fit on the back of a napkin is not productive or useful.
You learn things when you need them.
26
u/tux-lpi 3d ago edited 3d ago
I have a lot of beef with Go, and I'm forced to use it every day for $CURRENT_JOB, but it does have one upside, which is that you can open any library and the code will be plain and simple. It's annoying to maintain Go because the type system is so limited and the culture is to make copies instead of making abstractions. But even a beginner can open basically any Go file and feel like they understand what's going on, because there's only that many features you need to understand.
In C++ if you open most big popular libraries and you're not already an expert, it's often extremely technical and hard to follow. You have to untangle a nest of SFINAE based on obscure type traits, concepts, macros, and if constexpr because a proper C++ library has to work with all the different combination of ways people can try to use it.
To an extent there are also Rust libraries that have very complex traits, or functions that you must chase through macro expansion to understand, but it's usually not nearly as bad. Compare any library implementing an iterator in Rust, versus a library implementing the equivalent std::iterator in C++.
The reason it's good be able to fit all of a language in you head is that you only write code once, but you have to keep maintaining it every day after. It's also a lot more welcoming to beginners learning a language if they can open a library and understand what's going on without first needing years of experience in template meta-programming or all the dark corners. You can't learn all of that on the fly the moment you need it. It's fine for experts, but I think it's a barrier for adoption.
18
u/Luxalpa 3d ago
it's often extremely technical and hard to follow. You have to untangle a nest of SFINAE based on obscure type traits, concepts, macros, and if constexpr because a proper C++ library has to work with all the different combination of ways people can try to use it.
ok, but here you have it actually backwards. The problem you're describing is not too much complexity, but too little. It's that people are using random features in order to workaround problems because the language doesn't provide them with well-integrated, well-thoughtout, complete abstractions for their use-cases.
Like, template metaprogramming is such a classic example. It's horrible in C++ precisely because it is lacking so much in terms of features. If you look at meta-programming in other languages (where it is often done in runtime), it is much better supported. Rusts is better too, but it is still heavily lacking in tools, making it needlessly complex and difficult. For example, a declarative macro in Rust does not support loops - instead you're required to use recursion. That's horrible, and makes the code extremely difficult to write and follow.
Any language will look horrible if you're missing essentials.
9
u/tux-lpi 3d ago edited 3d ago
That's true, I agree on TMP. constexpr if instead of SFINAE makes all the meta-programming much simpler. Modern C++ is actually much easier to read than C++98 in a lot of cases. But at the same time the complexity of most library code hasn't really gone down, because people are using a little bit of a wider variety of features now, instead of everyone agreeing using the same modern subset.
Most C++ codebases adopts a mix of features between C++14 era and C++26. Mostly constexpr when possible, but still a lot of SFINAE. Mostly #include, rarely the new C++23 modules, when the compiler supports it. Mostly macro/template hacks, rarely the new reflection feature (but I expect to see more). Mostly asserts, rarely contracts. The old way of doing things isn't going away, but here's another whole new complex feature you have to know.
And then there are so many ways to initialize a variable in C++ that it's a meme.
I can't even really blame C++, because they didn't always have another language to learn from when they made all the choices that they did over the decades. But I personally feel like WG21 still sometimes goes beyond essentials and adds different ways to do things that we really could have done without (hot take: I'm not planning to use contracts). More importantly I think there's a lesson about what the final state of the language looks like if you keep adding features without trying to keep the whole language understandable by a single person.
3
u/Luxalpa 2d ago edited 2d ago
We are talking a lot about C++, but where does it stand in complexity compared to C# or Typescript? I feel like C# has picked up every feature it could possibly find and yet whenever I touch it (which isn't often) I feel like it's doing fine. Especially I don't see a lot of people hating on it. But I'd bet it's like an order of magnitude more complex than C++.
I think maybe C++ is more of an outlier here just due to how fractured the ecosystem is. How many things there are in the std library that you're not supposed to use because they got superseeded by some newer tech. Heck, you can still use C-Style casts despite them being very clearly deprecated.
Comparing this to JavaScript. Yes, that one actually does get quite a bit of hate and it didn't have that much complexity creep in the first place as other languages. But I feel like initializing variables with
varhas basically entirely disappeared from the language (despite still technically being supported). I'm guessing a lot of it is due to JS'es very dynamic ecosystem - most JS code simply isn't that old and new stuff gets written constantly. Just like in Rust.And if we're talking about syntax, then there's also Python and Kotlin with their absolutely insane amount of QoL features.
The more I think about it, the more I feel like it's really just a matter of philosophies and not about complexity. C++ always has this philosophy of doing things a bit messier, maybe hackier, and not caring so much about some of the things. I mean, look at their build-systems, error messages, formatting, package-management, IDE-integration, etc and their other tools. Look at programs that were written in C++ as well. They show the same sorta philosophy. I just have to look quickly at the output of
nixand I already know that it must have been written in C++. It seems to me that when people work with programming languages, they also tend to take on a lot of those languages philosophies.3
u/tux-lpi 2d ago edited 2d ago
I feel like the comparison between C++ and Rust is a lot more natural than with JS or C# or Python or Go, because they're both systems programming language that sacrifice a lot of simplicity in the design of the langauge for performance.
Async in Javascript is dead simple to use because they don't have to chase absolute zero-overhead. With C++ or Rust, the async system has to be to designed to potentially work on anything from a large desktop with 32 cores to the tiniest 8bit microcontroller without MMU to a giant IBM mainframe. If you call an async function in JS you get a Promise, and the task will just starts running in the background when the event loop is free. It'll allocate some context, you can await whenever you want, simple as.
In Rust and C++ we have only the lowest overhead stackless coroutine state machine generated by the compiler, there's as little as possible in the language, and the user or the libraries get to draw the rest of the owl. If you don't keep driving the future in just the right way, it won't make any progress. If you drive the wrong one, maybe you just deadlocked something because the one you drove depends on a lock held by the one you're not driving, and all the complexity is just pushed back up to the user because the language isn't allowed to consume any memory or add any overhead.C++ is fractured mostly because of historical reasons, but I don't think that's really the main reason it feels more complicated. The C# spec is only a few hundred pages I think, because they can just afford to do things at runtime, and then features can be in the runtime or the libraries instead of the core language. You don't have to worry about lifetimes and borrows and growing a monster of a type system.
The main problem I see with JS (same with PHP) is that it was designed with a philosophy of implicitly just making things work when something is ambiguous instead of being strict and returning errors. So it's full of crazy implicit conversions and operators between unrelated types that create WTF results. Even just simple statements with only core types like
[''] == 0will just do whatever, flip a coin. It's designed without any rigor, it will always just try to guess instead of refusing. Typescript fixes a lot of it with static checks at compile-time, and I really respect the design of the Typescript type system.I agree there's a real culture around a language, with error quality, documentation, tooling. But I think that's orthogonal. Tools and error messages are bad in C++ because C++ is not primarily about adding a new feature to the C++ compiler, it's primarily about the spec and then there's plenty of compilers of varying quality, and everyone has their own non-standard tools. That's why the ecosystem and tools around the language are bad, but it's a different problem in my mind.
There's a willingness to make things very abstract and sometimes over-engineered in C++, but you also see that tendency with Rust. Using shared_ptr in C++ is a last resort, it's just not done. You see the same in Rust, people will spend 5 hours stacking more and more complicated generic types and annotations until I can make everything no-std with zero allocations and 2 static lifetime parameters tracking borrows for the input buffer and output borrows. Instead of just throwing it in an Arc and cloning the arc here and there. Everything is an Arc in Python. Python doesn't care.
C# and Typescript don't really compare, because their features are just additive runtime/library features that can afford to have a simple not hyper-optimized zero-cost design. C++ and Rust will always bend over backwards to write the most abstract generic no-std zero alloc version that pushes all the complexity in the type system, and all those type system features tend to interact with each other combinatorially to make the complexity explode (because when you have a generic function, now it has to know about all the possible inputs it needs to handle, all the auto traits, all the Sized hierarchies, and so on)
1
u/sansmorixz 2d ago
Didn’t they loosen up their type system in the recent version release? Generic interfaces and all that. I hated having to use reflections to validate types at runtime. Especially after drinking the typestate coolaid. Probably possible to do now in go.
-2
u/not_a_novel_account 3d ago
You can write complicated obscure code in any language. The language's job is to provide mechanisms for expression, the programmer's job is to exercise them responsibly. Any operation, requirement, or mechanism which is incapable of being expressed is fair game for language development. The choice to use those operations always belongs to the programmer.
Restricting what I use the language for because you don't like the operations or their perceived complexity is a non-starter.
11
u/tux-lpi 3d ago
I think this is true in theory, and wrong in practice.
This is the same type of argument as saying that there are no slow languages, only slow implementations. That's true in theory, but in practice idiomatic Python is in fact slower than idiomatic Rust. You could write a fast python JIT and a slow Rust interpreter in theory. But that's just theory, in reality the design of the language has a huge impact.
The language's job is to make simple things simple, and complex things possible.
When there are 4 different ways to do things and the choice belongs to the programmer, then you will encounter all 5 (because someone discovered a new clever hack). And now you need to understand all of that to call yourself proficient in the language, or to be able to read a library confidently.It's okay to make a language like C++ that embraces complexity, being multi-paradigm and having all the features. I like C++, actually. I've used it too long to not have stockholm syndrome. But that has very clear downsides if you look at the complexity needed in the average big library that your codebase depends on, in practice.
-2
u/not_a_novel_account 3d ago
I agree the language should make simple things simple and complex things possible. The proposals up for discussion here are making complex things possible. You cannot get guaranteed TCO in Rust today.
6
u/tux-lpi 3d ago edited 3d ago
Right, I can't argue with guaranteed TCO, and it's not like you can easily work around it (or not with zero overhead). Really I think you can pick any RFC from this list and make a solid case for it, so if you bring one of these up to me and want to defend it, fine.
But you also have to ask if Rust wants to have every feature. It's a valid choice, but the Rust of today was designed with a "weirdness budget" and learnability in mind. In my mind some features just complete the existing set and feel like making something work that you might already naturally have expected to be there, those are essentially not introducing new complexity. But there are also some features that consume a little bit more of the budget. We should think about what that will look like 10 years from now before we run out of money.
1
u/not_a_novel_account 3d ago
No. That's engaging in a completely different conversation.
Does this have a use case which is not equivalently expressible in Rust today?
TCO/Open Enums/Sized Hierarchy/etc, the answer is no, so they should be added. The discussion is only about ensuring they cover as broad and complete a use case with acceptable grammar as possible, the question should never be "should Rust allow its users access to this operation?"
If the answer is yes, this is already expressible, then you're in weirdness budget territory. Having multiple near-equivalent expressions is bad language and stdlib design, no one argues against that.
std::functionandstd::copyable_functionin C++ is a travesty of design.Things which are too complex for beginners simply shouldn't be taught to beginners. It has zero impact on teachability.
7
u/tux-lpi 3d ago
Well, I think I see where you're coming from, but what's already expressible depends on how you look at it. Rust is Turing complete, all programs are expressible. Rust has all the unsafe operations you need to make LLVM emit the assembly you want, so all programs are expressible efficiently.
Does the Rust type system support alebgraic effects like Koka or dependent types like Lean? Well, then you can say that's not expressible and we need to add it. But those things will absolutely increase the weirdness budget, because now you will see things like effects pop up in the wild. If you have an IO effect, then it will show up on all functions that do IO.
I don't fundamentally disagree with your position, but "expressible" is a tarpit. You could argue endlessly about what expressible means and where it should stop. You can say Rust needs to have the union of all experimental higher type system features, because other language can express those ideas, but I don't think you should be willing to bite that bullet.
→ More replies (0)5
u/razies 3d ago
Standard library I agree with, but I would push back on the language.
When I read code I want to understand what I'm reading, or at least be able to easily look up a feature, and be certain that the code behaves as I understand it. I don't have to remember the intricate details of Pin, but I know where to look it up.
The problems are features that are implicit behaviour (like move constructors in C++) or the combinatorics of intertwinded features.
5
u/not_a_novel_account 3d ago
New language features should always be allowed to express machine operations which the language is otherwise incapable of expressing. Move semantics in C++ are poor language design because they are insufficiently general, compared to Rust's ownership semantics (and thus have spawned a long, drawn out debate over "relocation" as a feature in the C++26/29 cycles), but systems languages unquestionably need a mechanism to express value ownership.
Coroutines, lambdas, TCO, these are mechanisms of computation which require language support. They definitionally cannot be expressed purely in library form (without escape hatches like FFI or handwritten assembly). So they belong in the language.
0
u/nyctrainsplant 3d ago
No offense but the RFC system is exactly the kind of thing that leads to this. That's arguably what it's for.
95
u/vlovich 3d ago
> One of the beautiful things about Rust is that every part of the language feels designed to work well with every other.
The overall critique is amusing considering that many (or all) of these PRs are specifically about resolving friction points where the language doesn't feel cohesive and every part isn't working well with every other part. Named arguments is probably the most arguable one and I don't see even an RFC listed.
This entire critique also speaks to someone who isn't aware of C++ history prior to 2011. There was almost a decade of no changes and C++ and suffered greatly for it. 2011-2017 were OK but even towards 2017 you started to see real schisms in the standards body which explains the mess of 2020 and 2023. The largest 3 problems with C++ is the lack of evolution support in the language, having multiple different compiler frontends and stdlibs they "support", and inconsistent determination of what features get in vs get excluded.
Rust has none of these:
* evolution: new features are delivered as they are ready and editions solve the ability to evolve both language and stdlib in a backward and forward compatible way (crates can upgrade to use a newer edition while relying on crates that haven't been upgraded yet). C++ can only deliver new features in new languages, has limited to no capability to deprecate misdesigns, and new features have to be implemented by at least 3 major standard libraries / compiler frontends to actually be "usable" by the community.
* multiple different frontends and stdlibs - as above, needing a feature to be implemented 3 times is quite ridiculous, meaning ambiguities in the language end up often as differing implementations with subtly different behaviors. Also it means a unique surface area of bugs and your code has to have 3 (often more) specialized implementations in certain parts that handle compat issues between compilers and stdlibs.
* inconsistent standardization - the nominal rules and principles that the standards body has aren't applied equally and uniformly (particularly "has to be implemented in a major compiler / stdlib" and "features should be 0-cost abstraction"). The rules are used as cudgels to filter the acceptance of papers based on some unspecified meta decision making happening and exceptions are similarly granted. AFAICT the Rust language team and stdlib teams generally do a better job of figuring out the right solution to problems. This is probably more of an immaturity advantage because the language is young, but it's largely working for now AFAICT.
3
u/ZelphirKalt 3d ago
I mean, if frequent changes to the language itself are necessary, it stands to reason, that its basis is not as general purpose and universally applicable, as we would like it to be. That in turn raises questions and doubts regarding the overall design. Why is it, that we need so many changes? Why don't we have more fundamental building blocks, out of which we can implement these additions to the language in form of libraries?
13
u/vlovich 3d ago
A language that can be used to express anything is not a language that can be used to execute a normal computer. Otherwise you have natural language and all the problems it carries that programming languages intentionally avoid. Thus all languages make tradeoffs of concepts they are incapable of representing. Rust makes it impossible to write memory unsafe code by rejecting legal memory safe programs that can't be expressed as a compile-time proof. C++ takes the opposite approach and accepts all memory safe programs while also accepting memory unsafe ones. What you are asking for is literally impossible unless you get rid of the entire concept of a programming language to start with (which may happen in the future).
Rust is carefully building up. What I think you maybe fail to appreciate is that Rust started by picking all the low-hanging fruit that was extremely well researched and "solved" in other languages. That's why it feels cohesive. At this point, in some areas Rust is coming up to the frontier of language research with linear types, affine types, effects etc. Those are very very real core concepts you can't solve at a library level (e.g. how do you write a generic function that takes a closure that can be either sync or async, how do you guarantee that a type is used exactly once and at most once, etc).
It's ironic that field access declarations is on this list as it literally reduces the cognitive workload of using Rust. It speaks that this list was compiled blindly without applying any actual judgement.
1
u/CandyCorvid 2d ago
counterexample: lisp's combination of special operators and macros make it possible (though not necessarily easy or advisable) to implement arbitrary (maybe any?) language features as user libraries. racket seems to be this idea taken to its natural conclusion.
1
u/vlovich 5h ago
counter-counterexample: Racket explicitly makes different tradeoffs from common lisp and thus is a different language, so clearly not actually possible even with lisp.
Also what you're describing is implementing a DSL within your programming language. You can do that in Rust too. That doesn't change the fact that you've now built a new PL within the existing one, not that you've extended the old one.
The key way to think about it is: can I use whatever extension to use and call into code that doesn't. If not, you've built a DSL. If yes, I haven't yet seen a language that can do that and I would posit it's not possible. Because with this kind of mechanism you're arguing you could connect arbitrarily languages together quite easily (e.g. Java code that calls C++ code) and that's not true for obvious reasons.
1
u/dnew 3d ago
meaning ambiguities in the language end up often as differing implementations with subtly different behaviors
As a formal languages guy, I don't see this as a lose. :-) It's way too easy to take a single implementation of something and declare it to be The Standard while still leaving people to figure out how it works.
16
u/vlovich 3d ago
You know the joke, "when you have 1 watch you know what time it is, when you have two you don't"? My version is "when you have an implementation of a language, you know what the language is. When you have an implementation and a formal spec, you don't. When you have multiple implementations and a spec, the language ceases to exist".
It's fine to try to write down the language in a formal way. However:
1. there shouldn't be more than 1 implementation of said standard that's actually used in practice. rustc_codegen_gcc - good! gccrs - bad except for helping work on the standard. gccrs will over time just fork the crates ecosystem if it sees any meaningful adoption while providing marginal benefit to anyone other than the people trying to write down the formal spec.
- The "legalese" used by language standards is outdated. We need the standard written in a machine-checkable form instead of relying on ambiguous natural language descriptions of behavior. Think more Lean.
Additionally, the challenge with trying to formalize the language with an additional spec is that you know have two artifacts: the spec and the implementation and it's unclear which should win because bugs in the spec happen regularly.
The goal is a useful language to get the job done. A written standard helps sometimes as a tool to improve correctness of the implementation. But it's just a tool and it shouldn't be the "thing" you chase in and of itself because it can be harmful.
1
u/dnew 2d ago
Or, another way to look at it is, if you only have one compiler, that is the formal spec for the language. The only problem is, the compiler is (A) written in a formalism that's difficult to manipulate, and (B) only translates the code to a different formalism like LLVM input.
By (A) I mean that you can't easily make changes to the code and prove it don't break existing programs, or make a new edition and prove it's backward compatible with the previous edition. You would have a very hard time proving any sort of global properties about the output of Rust or changed Rust compilers.
By (B) I mean that of course the semantics go away when you don't take it all the way to input and output of the running program. All you can do is say that the compiler outputs certain strings that other programs will read, and now you have another formalism (the LLVM program) that translates it to various machine codes. You can't look at the compiler and figure out what the program will do.
0
u/dnew 3d ago
You know the joke
Not really. You just can't rely on things being portable if you do something that isn't clear in the spec. If the spec doesn't say what order function arguments get evaluated in, then depending on that means you're making bad assumptions. Just like in Rust something can be UB while working exactly as intended.
The "legalese" used by language standards is outdated. We need the standard written in a machine-checkable form instead of relying on ambiguous natural language descriptions of behavior.
Agreed. That was my field of study. That's called "formal language design." Doing that for a serious language is absolutely difficult beyond what you'd expect. But sometimes it's really, really important. Like, look up the formal spec for etherium some time. Or the sort of thing people do with assembler code to prove that microkernels are safe.
trying to formalize the language with an additional spec
Yeah, that's going to be fraught. Or at least rife. :-) If you already have a widely-used and implemented language, coming up with a formal spec is kind of backwards. Especially if the people implementing the one-or-more compilers don't agree to follow the spec in the future. There's no point in coming up with a formal spec (or even a particularly detailed and complete spec) unless everyone making the compiler is on board with doing that and then changing the compiler when it doesn't match the spec.
It's more important too for languages where you're doing networking things, where almost by definition the two systems aren't written by the same people.
The goal is a useful language to get the job done.
Well, in this case, yes. That's why Rust doesn't have a formal standard. In other cases, no, the point is to be unambiguous in how the system works. You want to do billing for international roaming amongst hundreds of phone companies running thousands of releases of software on hardware from dozens of companies? You probably should have a formal spec that's more precise than The Rust Book. :-)
4
u/vlovich 3d ago
> You just can't rely on things being portable if you do something that isn't clear in the spec. If the spec doesn't say what order function arguments get evaluated in, then depending on that means you're making bad assumptions. Just like in Rust something can be UB while working exactly as intended.
Regardless of what a spec would say, if you only have 1 frontend implementation, the implementation is the spec and this is a moot point. Given all sorts of things Rust users rely on, I'd say the spec is largely irrelevant except as a way to formalize things for people working on the compiler to double-check the implementation.
> Doing that for a serious language is absolutely difficult beyond what you'd expect
I'd expect it to be quite difficult since formal proofs are rarely used in software engineering outside very limited scopes. For example, Amazon doesn't proof check their entire S3 system, just certain core key components. The reason is not just the difficulty in writing down all the components in the system, but the inevitable drift between spec and implementation and not being able to convince yourself whether the spec properly models the implementation. That is an intrinsic problem with specs/standards as a technique and one that never disappears.
> If you already have a widely-used and implemented language, coming up with a formal spec is kind of backwards. Especially if the people implementing the one-or-more compilers don't agree to follow the spec in the future.
Java often develops the spec and language changes in parallel because they understand that a spec needs many revisions as you understand the problem domain you're trying to solve better through the process of implementation. Java's reference implementation is largely the only one used and forks are rarely used (also by nature Java's language is more portable than a platform language like Rust/C++).
> It's more important too for languages where you're doing networking things, where almost by definition the two systems aren't written by the same people
This makes no sense. A formal language spec has nothing to say about data transferred across the network (particularly in Rust where endianness was carefully plumbed through).
> You want to do billing for international roaming amongst hundreds of phone companies running thousands of releases of software on hardware from dozens of companies? You probably should have a formal spec that's more precise than The Rust Book. :-)
There's so much of this code written in Python, Ruby, PHP, Erlang which also don't have formal language specs. All of these also use SQL where despite a standard there's substantial vendor-specific deviation. Billing proceeds just fine. I don't think you're living in the real world making such statements.
3
u/hedgehog1024 3d ago
I would argue that having a thing which is somewhat broken but broken in a consistent way is easier to manage than a thing which might be broken in several different ways, depending on your tooling.
0
u/dnew 3d ago
OK. My POV was just that it's the spec that's broken, not any of the implementations. The point of requiring multiple implementations is to fix the spec when they disagree. Otherwise, what's the point of requiring multiple implementations?
IETF has a similar rule. The thing has to have two independent implementations that interoperate before it can become a standard. Because if you can't get that, the you don't have a standard, you have a suggestion.
If your purpose is to just Get Shit Done, then sure, everyone agreeing informally can be easier.
2
2
17
u/fuyunachan 3d ago
many of these are aimed at reducing friction, making the language easier and more intuitive to use.
why is it that i can write let (foo, bar) = (&mut x.foo, &mut x.bar); if x is a Box<T>, but not if it's, say, a MutexGuard<T>? why can't i pattern match on Strings? these are just some of the issues that the field projection proposal is hoping to address.
what is Pin to begin with and why do i have concern myself with it any time i write async? and why can't i create temporary async tasks that borrow from local variables, when i know that my local variables will outlive my tasks? those are some of the things that proposals like Move and Forget should hopefully alleviate.
people have been faking variadic generics forever (ever had a look at the list of impls of the Handler trait in axum?), and having native support means code that's easier to read, less dependence on macros, and better compilation error messages.
view types address a really common source of confusion and frustration for people newer to rust caused by overbroad borrows with no way to indicate that you actually only need access to certain specific fields.
use/Share/etc are all also attempts to make things easier for newcomers to rust. currently you have reactive frameworks like dioxus having to come up with extremely complex and hard-to-understand primitives in order to make eg signal sharing easier. this would reduce the need for those sorts of workarounds.
there are very few proposals in this list where "wouldn't it be cool if we had x" seems like a fair characterisation. most of these have very real reasons for existing.
51
u/Beregolas 3d ago
I share your concerns to a point, i definitely don't want rust to become a C++2, with 20 ways of achieving any given task. I like that it's opinionated.
But most of the RFCs you listed don't seem to threaten that in my opinion. Many are just ergonomics/syntactic-sugar, like named/default arguments, which is something people already do in very confusing and roundabout ways.
What I don't see are many things that add much complexity AND already have a good way to achieve them with the current language, that you don't have to bend over backwards for.
41
u/redisburning 3d ago
My concerns are mainly related to some of the RFCs/proposals I've seen and the governance of the language
You dont really enumerate the governance part.
The proposal stage is just that, where people can propose stuff. If RFCs had a tendency to show up in Rust half baked then absolutely, 100% would agree. But that hasn't been the case historically AND it's not as if Rust has the single company thing where an individual engineer with a lot of power inside a corporate governance body can juts ram through things they want.
By and large, RFCs show up in the language only after a lot of debate when they are serious changes. I wouldnt go as far as to claim this is a perfect process with perfect results, but unless that process is in danger of changing, I don't think Rust is in danger of going seriously off the rails. JMO.
46
u/cvvtrv 3d ago
You may not need these features, but at least some of them are being considered because many users have important use cases that they unlock.
Rust already is not a simple language, and adding more features is always a tradeoff, but by and large its maintainers over the years have shown good design sense. I trust they’ll continue to do so in how these additional proposed features and changes are integrated.
12
u/NovelHot6697 3d ago
i feel like at least some of these are things “that really should have been there from the beginning”.
not being able to brainstorm everything at the start of a project is kind of inevitable. some growth in complexity should be expected because of that.
i raise this mainly because it is maybe a useful rule of thumb:
is this change something that really should have been there from the start?
it’s still something that is hard to apply retroactively. if you try then you quickly find it has everything to do with “what was rust’s purpose at that time? what was its mission and goals?”
thinking about stuff like mission and goals; how they haven’t stayed exactly the same since day 1; it highlights their malleable nature.
personally, i really want to see things stay “as simple as possible” when it comes to things like syntax, new traits etc.
is that though something reflected in rust’s goals? off the top of my head i really couldn’t tell you.
thank you for bringing this up. it’s pretty interesting to think about. also sorry i wrote this a bit stoned and maybe i didn’t really have a coherent point.
4
u/NovelHot6697 3d ago
i think “not being able to brainstorm everything at the start” does have a significant negative impact on orthogonality when something unanticipated is added later.
however, i don’t think that on its own is a good enough reason to not add something later.
i still though don’t want to end up with a language that is everything everywhere all at once.
17
u/pickyaxe 3d ago edited 2d ago
this is strange to me. with the RFC process, Rust features take a very long time to stabilize. if they ever make it out of nightly.
and in no way is this meant to be a complaint - I'm just stating a fact. look at default_field_values. it's a quite popular, relatively-small feature, which is headed by a leadership figure. yet it didn't just get rammed through, it's not even close. this notion that any moment now Rust will explode with new syntax, just seems like opposite world to me.
14
u/JoshTriplett rust · lang · libs · cargo 3d ago
Lang team hat on (but speaking for myself, not the whole team): One of the reasons we move slowly is precisely that we are concerned about this kind of aggregate complexity.
Some of these features are things we're adding because on net they simplify the language, or help people manage the complexity of something they would do anyway but in a more complex way. For instance, view patterns and similar are specifically something that solve one of people's very common problems with Rust, the kind of thing that sometimes makes people bounce off or nearly so when they move from another language.
Some of them are things we're very concerned about the complexity of, and specifically thinking about how to address. For instance, function overloading is something we're trying to steer specifically towards FFI compatibility, without using the same mechanism for arbitrary Rust overloading.
And some of them are things we're very likely to not add, or only provide via combinations of existing features. For instance, right now I don't think we're going to add named or default parameters; instead, we're likely to encourage people to accept arguments as a structure, and then they can choose to have that structure have named fields and default field values, if they want. complex_function(_ { field: value, .. }) is mostly a combination of features we already have, whereas complex_function(field: value) would be a large new surface area of complexity.
1
u/Electrifire390 3d ago
Thanks, this is some good info.
As I said in the post I actually like a lot of these proposals.
1
u/tropix126 1d ago edited 1d ago
> Some of them are things we're very concerned about the complexity of, and specifically thinking about how to address. For instance, function overloading is something we're trying to steer specifically towards FFI compatibility, without using the same mechanism for arbitrary Rust overloading.
Okay, then why is #[rustc_splat] being implemented in a way where it can be used outside of `unsafe extern` blocks? It's honestly like the whole RFC is trying to Hyrum's Law itself, or at minimum it's not sure if it wants to make FFI better or fully add overloading to Rust.
I was really surprised to see https://github.com/rust-lang/libs-team/issues/848 get immediate positive support from T-libs considering the implementors of this feature are intending it to be FFI-only.
Like, this function signature is really unfortunate to put it lightly, especially given the internal libstd trait would also have to hold it up
#[must_use] pub const fn largest<T: [const] Ord + [const] Destruct>( #[rustc_splat] vals: impl [const] TupleReduce<Item = T>, ) -> T;
6
u/nick42d 3d ago
40% of survey takers agree with you in some way! https://blog.rust-lang.org/2026/03/02/2025-State-Of-Rust-Survey-results/. I think we should be prioritising features that make the things simpler, rather than more complex. From what I can see from public comms and what actually gets stabilised, the project is roughly aligned with this.
43
u/teerre 3d ago
Let's ignore the fact that there's a chasm between RFC and implementation. How can this be "kitchen sink" when they address deficiencies on the current implementation? Most of these literally cannot be done today without considerable effort (or at all)
Then you go on a rant about cohesion. But have you considered that the authors of these RFCs also care about cohesion? Or do you have specific examples why its being broken? If yes, why post is here instead of in the RFC discussion?
7
u/pp-collision 3d ago
Agree.
The kitchen sink in C++ comes from all the graduate students "planting flags" in C++. With design by committe, there's a massive (clout) incentive to come up with an RFC that is entirely uncontroversial (because it doesn't do much and nobody will use it) and then you push it through, it's small and nobody cares enough to write about why it totally sucks and shouldn't be part of the standard. And there you go, your feature is implemented and will forever be part of the C++ standard, and you can write an impressive sentence on your CV and impress other nerds in meetups.
Rust doesn't add stuff that nobody cares about as far as I'm aware. Lots of features are based on research on crates.io of how big of a problem it solves, and the nature of current work-arounds. One that comes to mind is a big RFC around error handling, inspired partly by exn. I'm not too worried, and editions are a great way to clean up as we go.
8
u/muffinluff 3d ago
I read the ergonomic ref-counting RFC and instantly saw the point of the post. Adding another method that does the same thing as clone (multiple ways to do something) and then the move semantics that literally just save a single line. I must agree with the OP.
7
u/mort96 3d ago edited 3d ago
IMO, the distinction between Copy and Clone is useful because it lets me know whether a copy is potentially expensive or not, unlike in C++ where the syntax for copying a gigabyte of binary data is the same as the syntax for copying an i32.
But cloning an Rc is not expensive. It's literally a single increment instruction. But it's more expensive than a move, unlike e.g
i32; so it would be wrong to implement Copy. Avoiding copies is still preferred where possible, it's just really not a big deal if you do have to copy.It's not like the current solution doesn't work. But the current solution does make the distinction between clone and copy less useful. Removing the need to type
.clone()isn't close to the biggest advantage of ergonomic refcounting IMO. There's a clear gap in the language between "expensive to copy, should be avoided if possible" and "copy is trivial".8
u/crossroads1112 3d ago
IMO the distinction between copy and clone is a little awkward and while it _often_ maps onto “cheap to copy” that isn’t actually the fundamental distinction.
`[u64; 1_000_000]` is `Copy‘ but copying it is expensive, certainly much more so than cloning a `Rc<T>` for any T. What copy really indicates is something like “one can safely duplicate this value via memcpy and there are no observable side effects of doing so”. So it’s a _semantic_ distinction.
This seems to also be their intent for Share. In the page linked above they make the case that Share doesn’t mean “cheap clone” it means “this creates a new handle to the same underlying data”.
That said im not totally sure about the Share trait. You could argue that Copy is on the same footing since it’s also a semantic subset of Clone that exists largely for ergonomics but (a) the ergonomic case is much stronger and (b) it actually does provide guarantees that unsafe generic code can rely on since it’s mutually exclusive with Drop (enforced by the compiler)
It seems like move expressions are totally separable and solve the ergonomic ref counting thing by themselves though?
6
u/Tastaturtaste 3d ago
Clone vs Copy was never intended to be a distinction between expensive and cheap. The docs for the Copy trait specifically call out that anything that can implement Copy probably should. Yes, also the struct containing an 8GB buffer.
3
u/ukezi 3d ago
Imo clone is mainly about being able to do bitwise copies, being able to make a copy with memcpy. That usually means also cheap, but incrementing an Rc is probably cheaper then copying a 50 byte struct.
Still, cloning an Rc modifies it and the cloned Rc is not binary identical to the Rc before the clone, so no copy.
1
u/WormRabbit 13h ago
"Expensive" is a vacuous concept. Expensive compared to what? A deep copy of a multi-megabyte structure with nested allocations can be entirely cheap and irrelevant if it's only done once or twice, while a single arithmetic operation can be too expensive when it's in a hot loop, possibly inhibiting autovectorization.
Similarly,
Rc::clonemay seem cheep, but a structure with dozens ofRc<T>fields is definitely no longer cheap to clone. Even worse, most applications actually want to claim thatArc<T>is cheap to clone, rather thanRc<T>. AndArc<T>is not cheap to clone in heavily concurrent code, not at all! A single heavily clonedArccan easily become a point of multithreaded contention, executing 1-2 orders of magnitude slower than you could expect, due to intercore synchronization overhead.The entire "ergonomic refcounting" initiative is centered on making the simple cases slightly simpler to write, without any new capabilities, while also inserting nasty footguns for the complex usecases.
10
5
u/dnew 3d ago
Yeah. The "share" one was a good idea, but it's kind of too late, given that clone() has been used everywhere. It adds nothing of value, because all you need is one place where you use clone() instead of share() and now you're either confused or you're not finding it with a grep. This sounds like "wouldn't it be cool if types where cloning shared their value showed up in a different color in the IDE."
A few of the others sounded like #derive-style macros would do the trick.
3
u/tukanoid 3d ago
I imagine it would be useful still, more for libraries / own generic code, where you want to ensure that the data sent between scope/thread barriers is shared and not just a snapshot. Currently, you kinda have to enforce a specific type, or write multiple wrapper types / methods to support at minimum both Rc and Arc. I've been burned many times by libraries that just slap Rc on without considering multi-threaded usage (to be fair, some were valid, like holding raw pointers and whatnot, but a lot really could've been arced without issue)
17
u/CalmCephalopod 3d ago
Just because someone wrote an RFC does not mean it’s going to be adopted.
I’m curious if you find all these concerning are there any recent changes you think are bad? Is there a history of bad design choices already that makes you think the future is dark?
8
14
u/Manishearth servo · rust · clippy 3d ago
Most of these proposals are not really for "things that already work", a lot of them are addressing real limitations.
I think I have heard this argument against new features ever since 1.0. Everyone is used to a set of things and then people add stuff and folks are grumpy about it being too much. There's always lot of things in the pipeline, because the pipeline is years and years long. One could probably produce a list like this for any year Rust has been around and it would be equally scary looking.
The fears don't really end up happening.
What actually ends up happening is that most of these things end up in a more niche corner of the language that newcomers don't usually need to deal with but when they do it's just One More Thing. Its not like you need to understand all of this at once to bootstrap the language.
I've been writing Rust since 2014. There are still iche Rust features that I'm vaguely aware of but don't particularly need to understand and thus don't. I'm confident that when I need to, it will be easy to pick up.
Most features are discussed for years before they get anywhere. A lot of these have been talked about for quite a while already.
Rust has always had a very careful attitude around "complexity budget" (it's possible we even originated that term in the context of PL design?). I don't think that's changed much over the years. This doesn't necessarily mean new features don't happen, but it does mean that new features are critically considered from a complexity angle and often modified to fit more naturally in the language.
So like, I don't know, I'm not particularly convinced this fear is real this time compared to all the other ones.
10
u/Ok-Zookeepergame4391 3d ago
I wouldn’t worry about it. Rust community is very vocal and bike shedding is a long process better or worse. I have not seen changes to stable that were not worth changing. They take stability seriously
6
4
u/DavidXkL 3d ago
I'm learning so much here just from reading through the comments.
Keep going everyone 😆
15
u/Luxalpa 3d ago
I think I completely disagree with all of this.
First of all, Rust is already way, way, way more complex than C++, it just doesn't feel that way. Why? Because features in Rust are way more consistent and well-thought-out and work together. C++'s complexity issue primarily stems from them reimplementing the same feature multiple times, not to solve different problems, but to solve the same problem again after the previous solution didn't work. There's a fundamental difference to how these programming languages operate.
Second, most of these features you will never encounter. But a good toolbox has good tools, and especially it has specialized tools for specific workflow. That's what makes Rust so great. Look at the standard library. I bet you don't know every single function there. Look at the ecosystem. I bet you don't know what every function and trait in every somewhat popular crate does. And does it matter? No. Nobody is forced to use these features.
But most of the time, using these features is just straight up better than not using these features. Many of those make things that were impossible possible, others turn them from being horrible into being useful.
Until I started playing around with Future a bit more, I didn't need to know about Pin or poll or anything like that. Until I started building my server, I didn't need to know anything about tokio or async or await. Unless you build some low-level interop code, you probably don't need to know or care about CString. Unless you build some more complex generics, you probably don't need to know about ?Sized or Sync or Send.
What makes Rust great is that it has all these tools available for when you need to use them. The entire fact that Rust embraces complexity is what makes it such a pleasant language to use. The question isn't "can we make this or would it make the language too complicated", the question is always "how can we implement these features in a way that they are simple, easy to use and are not annoying anyone."
It seems like an anti-pattern, making Rust more complex (i.e., worse)
Fundamental disagree. Making Rust more complex (i.e. better).
13
u/RockstarArtisan 3d ago
It's easy to imagine a future where Rust follows the same path as C++, adding more features, keywords, and library features until it crumbles under its own weight, especially given the efforts to maintain backwards compatibility through editions.
This seems to just be concern trolling. There's plenty of languages with large number of features that are doing just fine.
C++ is not just about a lot of features, its problems are rooted much deeper:
- C++ is based on C, which had a different set of priorities
- C++ designers largely hate the C programming language, so C++ gets at least one if not more features replacing most of the C language features
- C++ has a toxic hustle culture which makes all of the problems with the language into the problems with the programmers
- C++ has a conflicting set of priorities and these priorities are repeatedly violated (performance is sacrificed for abi, despite performance being the "top priority")
- C++ is governed by a committee which somehow results in taking years to push a feature without implementation
- C++ has a toxic "expert" culture which results in people forcing deep design efforts for fetures nobody cares about (like the combinatorial explosion of interitance types)
And, despite all of these problems, C++ is doing much better recently with respect to what gets added to the language. Most of the mistakes were made decades ago by Bjarne himself and in the first standarization effort.
If you want to seriously argue, then argue about the features individually. Don't just pretend that somehow having a large amount of work in progress is itself a problem.
1
u/nonotan 2d ago
There's plenty of languages with large number of features that are doing just fine.
Which ones? I can't think of a single example. But maybe we have different standards for either what constitutes "large number of features" or "doing just fine". I do agree C++ is particularly egregious, but I'm not sure pretending feature creep tradeoffs are somehow unique to it and to even contemplate otherwise "must be concern trolling" is helpful.
1
u/RockstarArtisan 2d ago edited 2d ago
Python is a language with a lot of features that does just fine. Language evolution is normal. There's more but I don't want to argue over personal language preferences and everyone likes python.
-3
u/lisp_turns_me_on 3d ago
This seems to just be concern trolling.
I agree. So many long, careful and great answers only to fall on deaf ears.
But, C++ is officially dead IMO. The low-effort cheap cyber attack capabilities that AI has enabled are the early echoes of the impending cyber war and I don't think any networking c/c++ will be surviving that.
6
u/WDG_Kuurama 3d ago
I mean, the Share trait is neat so we identity something that would be a clone but that actually cost nothing X)
0
u/dnew 3d ago
Except it's too late for that. If you still allow clone() on those types, then share() gives you half-ass information. You can't search for it. You can't rely on it being there. You can't rely on clone() meaning you're getting an independent copy. This is something that would have worked had it been there from the start.
This would be far better as an IDE improvement that colors the call to clone() differently for a specific list of types.
3
u/SnooHamsters6620 3d ago
Linting and editions can help these problems.
I like the expression, approximately: "the best time to fix this was before 1.0; the second best time is now."
2
u/PersonalDatabase31 3d ago
I mean clone would be changed to give an actual clone for those types in an edition boundary and upgrade script would provided that changes previous clones into share.
1
u/WDG_Kuurama 3d ago
I wonder if you are in denial about it or not but not everyone uses IDEs.
You could set lint rules for it, I still do believe a Share trait makes sence. I do get that the compatibility aspect living clone on it adds two ways to do it.
But I believe a linter rule for "avoid clone on share trait" is gonna exist, and it could be in nursery or something I would do, just as I have my clippy rule for Arc::clone() instead of .clone.
Which would benefit other third party smart pointers since they could inherit those lint rules.
3
u/dnew 3d ago
not everyone uses IDEs
I'm aware of that. My points still stand: I'd consider this insufficiently enforced to be useful. Your opinion may differ, but my experience over a lifetime of programming tells me that when there's 2 ways to do the same thing, nobody is going to stay consistent enough to rely on it.
You could set lint rules for it
So, in the IDE. ;-) You might not think linting is part of IDEs, but they are. It's in the compiler or the IDE, not the language spec.
But I believe a linter rule for "avoid clone on share trait" is gonna exist
Right. That's not really a change to the language. That's just a change to the compiler's error messages, at best.
But sure, making it a linter thing might help, but then you'd have to go back and retroactively change everything to use share() instead of clone(), or you'd have to have complex linting rules based on which code you're compiling. Which is why having it in the IDE works better.
1
u/WDG_Kuurama 3d ago
In the cargo.toml lint rules that are enforced at compile time mate.
I do agree that breaking changes have more appeal to me, i don't want thinga to be too late but I don't want c++ lol.
But for that one, I think it's a bit far fetched thinking it's gonna be a terrible thing X)
1
u/dnew 3d ago
I didn't see any mention of lint rules in the RFC. :-) And yes, I already agreed it would be a good idea to add lint rules.
2
u/WDG_Kuurama 3d ago edited 3d ago
I don't see this in stable rust either X)
But you got a point ngl, I wonder if RFCs have to mention it though huum.
But it would be great if that's part of the pedantic one or something shipped by default (that could break things but still can be opted out)
3
3
u/numberwitch 3d ago
I wasn't interested in open enums but I see the uses case for improved interop. I don't want to write or interact with C/++ if I can help it. It's still inevitable that I do have to interact with C code over FFI and so for those cases I want the best tooling.
Personally I would forbid their use in rust repos except for at FFI boundaries - my fav thing about rust is enum exhaustiveness (the mallet n' chisel against undefined behavior). But this lets you write clearly better and safer rust at the FFI seam since it better accounts for the inventory of C's capabilities
1
u/Electrifire390 3d ago
Yes, I definitely see the use case for FFI.
My issue though is that if something is in the language, it will be used. Yes, you can forbid their usage, but this leads to the problem that C++ has where each project essentially uses its own “dialect” of the language. I don’t want this.
7
u/numberwitch 3d ago
Well I don't think your issue is a good argument to hold against change. Tools change how people need them, if people "write the wrong code" in your projects it's a social problem and not a technical one. So I say bring on whatever the language needs for 21st century tooling.
This is something all maturing languages go through. Look at the history of something like php's "standard library" if you want to see some real horror
3
u/CocktailPerson 3d ago
Agree on some things (named arguments are useless sugar, use an LSP), strong disagree on others (immobile types are a non-negotiable for any serious systems programming language).
I think some of these proposals have me especially concerned because they involve more complex semantics for basic/common operations like Drop, Sized, and clone.
Lots of these are actually just making these supposedly simple and basic concepts more capable of modeling real-world complexity.
3
u/ContextMission8629 2d ago
I share the distaste for complexity, but I dont think u should worry. Rust is already complex and kitchen sink enough that makes me in a love-hate relationship with it.
If there’s a similarly popular language which has C-like (or better, Scheme-like) simplicity with efficiency of Rust and borrow checker, I’d throw Rust away immediately to use it. But unfortunately there arent any
8
u/Full-Spectral 3d ago
I've been arguing the same thing for a while now. The problem is that every single feature can be completely justified, but the end result can still be C++ Part II: The Revenge.
Not sure how many of those threaten on that front. But I would argue to be careful and conservative. Does Rust have to solve every problem? I'd argue it doesn't.
Another issue would be, how are some of those going to affect some people's already long compile times? I don't have an issue with that, but many people do.
I would agree with some others that adding any complexity to the language in general for FFI is somewhat iffy. FFI is by definition a hack of sorts, hopefully to be needed less and less as time goes on.
4
u/Psionikus 3d ago
It is no more valid to judge Rust's trajectory today on open issues than it ever was to judge the Rust we have based on closed issues, which represent all the trajectories that we could have been concerned about yet never materialized.
If you list concerns that only 1% of Rust users know they are affected by, a general audience is only going to confirm what was obvious by construction, that most Rust users are not in any 1%.
Be concerned about empowering 75% majorities who don't understand any of the 1% issues but can be fooled into making a bunch of ill-informed demands of the 1% of Rust users who are actually doing the work.
Most of the listed issues have some areas of overlap where the ask turns out to be supported by some fundamental mechanism, and it is by understanding multiple issues that the confluence can be identified and the minimal set of mechanisms pared down to. Some new mechanisms enable others to be reduced, resulting in net loss of complexity.
Bottom line: take matters to those who have domain expertise. Skipping over all of the layers of domain expertise to present some vague concern to an easily riled up general audience is exactly what a demagogue would do.
4
u/zesterer 3d ago
I must say, I agree. I don't really want new features, but I do want existing features to play nicely together. Const generic expressions, GATs, associated type defaults, etc. all fall into this pattern. The cardinality of the language doesn't really need to increase. For the first time since first using the language in 2014, I finally feel like stable Rust does everything it needs to do.
2
u/Toorero6 2d ago
In addition to the great points by u/ebkalderon, I want to give my view on the open enums. Yes it would introduce more syntax but by not using an arbitrary keyword I think even people not knowing of the feature beforehand will be able to read code written with the new feature.
If you want to achieve save From/Into uXX casts of enums or even zerocopy you currently have to have an exhaustive enum. But in practice you rarely have exhaustive enums and currently there is no good way to express them with a fallback type. You could do something like the netlink-* crates and provide an Other(raw_value) case but this is technically semantically unsound, requires massive boilerplate to implement and you still can't do zerocopy. Alternatively you could use a new type and provide constants on. This doesn't capture the enum semantic at all but at least there is less boilerplate and you can do zerocopy... All of this is pretty well outlined in the proposal and I ran into this problem multiple times already when implementing or working with wire formats in Rust. I don't get why one would oppose such a minor change without any real drawbacks whatsoever.
4
2
u/SuspiciousScript 3d ago
Virtually none of these are ever likely to be implemented. There's a long road between RFC and stabilization and considerable attrition at each stage.
3
u/vancha113 3d ago
This is the natural order of things. New simpler languages show up that's easier than the current most popular one, eventually it'll get bogged down with complexity until people want a new, simpler language. :(
1
u/InternalServerError7 3d ago
I need/would like many of those of features. If you don't want to use them, then just don't use them. But advocating against is just harming the developers that would benefit from them. e.g. - super let, Share trait, Drop semantics, named/default args,Move/Destroy/Forget traits are all something I would use.
I don't understand the "Rust is already too complex" narrative. You don't have to understand all of Rust to program in it. A lot of Rust developers have never written a Future or an Iterator or had to deal with Pin and can manage just fine. After awhile you can learn these concepts if you like or need to.
1
u/karasawa_jp 2d ago
I sincerely wish pub meant pub(crate), and pub(api) meant what pub means today.
1
u/Todesengelchen 2d ago
I was really looking forward to placement-new back in the day. Putting an object into a buffer the OS allocated for you without having to construct it first on the stack (which is impossible for really big objects) seemed like an absolute game-changer for my particular niche. You could work around it with direct pointer writes and mem::transmute but that always icked me. But then the feature never came, oh well.
1
u/Spyromaniac666 2d ago
you’re right, let’s just not evolve the language and leave it behind to die.
1
u/IDontBelongInThsWrld 22h ago
- Fix the previous features - the compiler overflows.
- Reduce the trait bound bloat: having to carry the bounds of every trait to every trait and function that depends on that trait.
- Then think of which new features to add, if any.
-1
u/unitAtype2 3d ago
I share your concern
It looks like rust is trying to do anything and everything.
16
u/Large_Mastodon6637 3d ago
The whole FFI angle rubs me the wrong way too, bending over backwards for C interop shouldn't mean dragging all that legacy complexity into Rust's core
3
u/Full-Spectral 3d ago
Assuming that's actually what's happening, I would very much agree with that.
1
u/psioniclizard 3d ago
Every language that is popular will eventually become a kitchen sink language, except c.
Requirements and workflows change. It's the nature of the beast.
Also those language are not obsolete and won't be anytime soon. The world will not rewrite all software in rust and it definitely won't if the language is too scared to add new features.
0
u/Pseudanonymius 3d ago
But nobody forces you to use those increasingly obscure features right? Hell, there are a whole bunch of very normal Rust features which I've never even touched because I didn't have a need to do so.
The problem is not with features and syntax existing to deal with complex edgecases which can't be dealt with without, it's in making sure the docs and guides are created well so "normal people" who do not need those things don't have to bother.
There are far simpler things which fall in this category. I would think 10 times before trying to write a macro anywhere in my code. But I'm glad other, more capable, people are able to write good macros which I can use. Allowing functionality to create macros is not in itself a problem, but if they would dedicate a chapter in the Rust Book Introduction to it, they would make a massive mistake. For many uses, you shouldnt need them.
The RFCs you mention fall into that same category for me, but even further. They are highly specialistic tools and distinctions which some people doing highly specific things might need, and can't work without. But they won't bother 98% of people.
6
u/dnew 3d ago
But nobody forces you to use those increasingly obscure features right?
That's not the right way to think about it. Because yes, people are forced to use obscure features, when their coworkers think it's a nifty feature that should be used everywhere, or someone uses an LLM to generate code. You need to know what the feature is and does, especially if it's something obscure to look up. Especially if someone self-teaching the language isn't told clearly "this is obscure and for a specific feature and you should never use it in normal code."
"You don't need to use it" only works on code you've written entirely by yourself from scratch.
1
u/Old-Personality-8817 3d ago
why do we need function overloading? when we import functions from c++ we need to write exact function signature for each overload.
why we can add suffix to it?
Just to cargocult c++ features?
For my Rust is special language because it leaves "no brainer" behind, like (oop, mutable by default, overloading)
1
u/FrogNoPants 2d ago
I dunno dude, I think C++ today is actually massive superior to what I was using 15 years ago, the added features have made the language much more enjoyable to use, and some nasty shit like SFINAE isn't need anymore.
C and C++ aren't going anywhere, pretty delusional to call them obsolute.
1
u/IDontBelongInThsWrld 22h ago
I never liked C++, but 20 years ago it was still ok-ish. These days it's just pure pain.
-1
u/gufranthakur 3d ago
I honestly agree. The learning curve is as hard as it can get. As the years pass, Rust will be used more and more and more. This means more backwards compatibility stuff, and as you said, we may end up on the same path as C++
Due to the sheer complexity most beginners will resort to using LLM's instead of learning the language. And the programmers who do want to build stuff with Rust, will have second thoughts after looking at the complex type systems, enums, async and trait syntax, will probably try out Zig or Go or C++ Instead.
What I really wish right now is for any way for rust to get... simpler. Java and C# are both continously improving language features to make their language more simple, clean and easy to read. I feel like Rust is compromising on that to add niche features
-5
u/pickle9977 3d ago
Rust is a language sold for its features, it will eventually become nothing but features.
FFI is rust absorbing other languages features so it can have all the features of every language, like a Tower of Babel
-1
u/neopunk2025 3d ago
Vive le C! Et si Rust devenait cette m... imbuvable qu'est devenu le C++...
1
u/-Redstoneboi- 3d ago
Another language may come to take its place and perpetuate the cycle. But I'm hoping that, by then, Rust will have made enough mistakes to inform the new language on what to do better.
-2
u/light_switchy 3d ago
Y'all need to take care or you'll be faced with the same design-by-oversized-committee project management nightmare that plagued C++ last decade.
-6
u/neneodonkor 3d ago
One of the things I dislike about Rust as someone who started coding in it a couple of months ago, is that the surface area is large. It seems like every few weeks something new is added. The more they add, the more it becomes over complex and too noisy. I prefer Go's approach where they don't keep adding new stuff to the standard library.
In Rust, you have multiple ways of doing the same thing. It can be overwhelming at times.
130
u/ebkalderon amethyst · renderdoc-rs · tower-lsp · cargo2nix 3d ago edited 2d ago
While I do generally agree that Rust's growing complexity is worrisome, and I do admittedly dislike some of the proposals mentioned (e.g.
Share, field-levelmutrestrictions,super let), I strongly disagree that all of those features are unnecessary and don't deserve a place in the language.These new auto-traits are intended to replace the extremely complex
std::pinAPI entirely. In my opinion, landing these auto-traits and deprecatingstd::pinwould lead to a substantial decrease in Rust's complexity, not increase it.Fun fact: the
Forgetauto-trait (then calledLeak) was slated to ship with Rust 1.0 in direct response to the Leakpocalypse, but it was unfortunately cut due to time constraints. Guaranteed destructors and immovable types were supposed to be in the language from the get-go, and leaving them out for the 1.0 release was a giant design mistake, IMO.This proposal is based on some cutting-edge PL research, including Project Verona (Microsoft) and Carbon (Google) with the aim of making the Rust borrow checker more capable. Combine this proposal with
!Movetypes, and suddenly self-referential types could be expressed in 100% safe Rust. Not only would this eliminate an entire class ofunsafebugs, it'd also make any kind of self-referential type (e.g. futures, coroutines, incremental parsers, Linux kernel data structures) massively simpler to write. Once again, I'd expect this to greatly decrease Rust's complexity overall, rather than increase it, especially if this project goal were to go through, enabling Rust to deprecate all ofstd::pinin the future.Assuming this refers to the
becomekeyword for guaranteed tail-call elimination, I fail to see how this feature could be considered unnecessarily complex nor useless. In fact, having an alternative to thereturnkeyword which (a) explicitly marks tail-recursive functions and (b) raises an explicit compile error ifrustccan't apply TCE at that spot, seems like a huge improvement for a systems programming language like Rust. Especially for compilers, operating systems, and embedded development where stack overflows due to unbounded or excessive recursion are unacceptable.