Looking at the rust rfc for the lightweight clones feature [1]. It took me a while to sort of understand it. Once I did I was excited for the feature but after awhile I was once again struck by the observation that rust is a very complex language to learn.
To me as someone who's not learned either it looks like all the concepts and features of 'true' modern C++ (as opposed to C + a few extra features) spliced with the features and concepts of Haskell.
Yet people seem to like making useful things in it so it must have gotten something right. So I'll probably get around to attempting to really use it again.
In my experience, C++ is a much more complicated language. The 8 ways to initialize something, the 5 types of values (xvalues etc.), inconsistent formatting conventions, inconsistent naming conventions, the rule of 5, exceptions, always remembering to check `this != other` when doing a move assignment operator, perfect forwarding, SFINAE, workarounds for not having a great equivalent to traits, etc. . Part of knowing the language is also knowing the conventions on top that are necessary in order to write it more safely and faster (if your move constructor is not noexcept it'll cause copies to occur when growing a vector of that object), and learning the many non-ideal competing ways that people do things, like error handling.
Consider a move assign to a vector<int> from src to this. The first step is for this to free its resources, the second step is to assign src resources to this, the third step is to set src resources to null.
If src and this are equal and you don't check for it, then you end up destroying src/this resources and the end result would be an empty vector (since the last step is to clear everything out).
Right you said move but I was thinking of swap for some reason.
I probably still wouldn't care, unless it's clear that moving to self is even required. Trying to break everything in a thousand pieces and generalizing and perfecting them individually is a lot of busywork.
Complexity is not necessarily an automatic dealbreaker for a Rust language proposal—lots of Rust features are complicated because they solve problems that don't admit simple solutions—but the purpose of this particular feature is to make it easier to write code that uses reference-counted pointers and reduce how much stuff the programmer has to keep track of. It doesn't let you do anything you can't already do, it just makes it less clunky. So if people can't easily understand what it does, then that's bad.
As a side note, this might just be a me thing, but I distinguish between "complex" and "complicated". Certain things have an inherently complexity. Stripped to their essence, they're still going to have a lot of moving parts. It's just their nature. However, you can also add complication on top of something that makes it more complex than it needs to be to perform its function. Think "complication" in the watchmaker's sense. It might be neat to add a dial that shows the number of weeks since your last dentist appointment, but you don't really need that to have a functional wristwatch, and its presence makes the whole thing a lot more finicky and fragile than it would otherwise be.
Complexity is fine. Sometimes we're working on complex problems without simple, straightforward, correct solutions. I do try to avoid complications, though.
Yeah, I was slightly sloppy about that, but also the distinction doesn't entirely help at this level because people get into vicious fights about whether a particular bit of complexity is really "necessary". E.g., lots of people argue that async Rust is unnecessary complication, either because you can just write all the state machines by hand (which is terrible for expressiveness, but do you really need expressiveness?), or because you can just spawn an OS thread for every task (which is terrible for performance, but do you really need performance?). Whereas the pro-async perspective is, yes, it's very complex, but nothing simpler would have done everything that it needs to do.
Oh, I hear ya. That makes perfect sense, and you're so right: my idea of a complex solution might be someone else's complication, and vice versa. I didn't mean to disagree with you, and surely not to correct you, because not everyone agrees on the distinction I make between the ideas.
I meant that more in the spirit of "oh, while we're talking about this, here are my thoughts on a tangentially related idea".
I find that Rust is a language that feels insurmountable when you examine it from a distance but once you get your hands dirty it feels natural very quickly.
Case in point, my first read on lifetimes just left me confused. So I used Rc<> everywhere and made a perfectly functional program. Picking up lifetimes after mastering the basics made it a lot easier. Most people won't even need to care about lightweight clones.
> Case in point, my first read on lifetimes just left me confused. So I used Rc<> everywhere and made a perfectly functional program.
Curious, did you run into lifetime issues or just started wrapping everything in Rc<> after reading about lifetimes? Wrapping everything in Rc<> isn't even a bad thing, that's what you have to when you do WASM in a browser.
I still find it confusing sometimes because you're not setting lifetime, you're just giving it a name so that the compiler can reason about it.
Like saying something 'static doesn't make it static, it supposed to mean "live until program closes", but you can totally create things with 'static and free them before program exits.
It's been so long I can barely remember now but I'm pretty sure it was the lifetime propagation that got me... once one struct had it, every struct using that struct needed one, then there was more than one lifetime and combining them (and establishing the rules of how the lifetimes related to each other) bent my brain out of shape. Rc<> let me sidestep all of that.
Yeah, lifetime in structs are PITA. However, IMO using them is "the right choice" only in certain situations where performance hit from not using it is too much. I get why serde uses it, but notice who those don't get exposed to end user that much.
Yeah, once you're putting lifetimes in structs you're liable to run into trouble. In general you only want to do that for structs which are not expected to have long or complicated lifetimes (i.e. if you're expecting it might be allocated on the heap, it's likely too complicated to be workable)
Any time I need to explicitly give a lifetime in a struct I use an Rc or some other container. I’ve been using rust casually for years and every time I try to appease the compiler with lifetimes I realize it’s not worth my time. If someone has a very ELI5 resource that will make me understand when and why to do this I’d appreciate it.
IMO this is something that should just be handled by extra runtime code or a magically smarter compiler. Lifetime management feels like something that matters in a microcontroller or hyper-optimized setting, but never when I’m executing code on Apple Silicon for a random application. And yet the language makes simple GC ergonomically painful. I love the language but don’t really need all of that performance. I would gladly take a 1% hit to increment reference counts.
there is currently a lot of ongoing discussions about "easier light weight clones" not just in the context of the RFC but in general.
And from all the discussions I have seen this RFC is one of the less promising ones as it mixes up the concept of "implicitly doing an operation before moving something into a closure scope" and "light weight clones" in a confusing ambiguous way.
So I don't expect it to be accepted/implemented this way, but I expect something similar to happen.
Like for "light weight clones" use is a pretty bad name and new syntax isn't needed, if we start shortening a `.clone()` to `.use` because it saves 4 letters then we are doing something wrong. Similar if argue for it for niche optimization reasons instead of improving generic optimizations to have the same outcome we are doing something wrong IMHO.
And for the the scoping/closure aspect (i.e. a rust equivalent of C++ closures `[]` parts (e.g. [&x](){...}) then it also seems a bad solution. First it's a operation which relates to the closure scope not the call inside of it, so attaching it to the call inside of it isn't a grate idea. Especially given that you might have multiple places you pass a clone of x in and this leading to a lot of ambiguity not highlighted with any examples in the RFC. Secondly for this concept it isn't limited to "cheap clones" sometimes you have the same pattern for not-so-cheap clones (through it mainly matters for cheap clones). And lastly if we really add ways to define captures, why not allow defining captures.
Now sure if you have a good solution for more compact way to handle "not-copy but still cheap" sharing (cloning of handles/smart pointers) of values there it maybe could make sense to also allow it to happen implicitly outside of closure capture scope instead of the invocation scope. But I would argue it's an extension of an not yet existing handle/smart pointer ergonomic improvement and should be done after that improvement.
(yes, I'm aware they use `use` because it's already a keyword, but making a non-zero cost copy of a handle/smart pointer isn't exactly "use it" but more like "share it" :/)
As someone who’s dipped their toes in it, I’d say Rust is complicated in theory but not as complicated in practice. I said on a different topic that LLMs help a bit with suggestions and you can start with suboptimal code, improving it as you get more confident.
Obviously I’m not building anything production ready in it but it’s been useful for a few apps that I’d previously been using Python for.
> Looking at the rust rfc for the lightweight clones feature
while the Use trait (a better name is proposed [1]) is useful, but I don't see how the .use syntax adds any value over .clone()? If the compiler is able to perform those optimizations, it can also lint and ask the user to remove the unnecessary .clone()/change x.clone() to &x. IMO this is better than the compiler doing black magic.
Aside from the complexity not being possible to misuse (which is trivial in C++), I still find it easier. Moreover, even if you didn’t know about this if you use clippy it’ll suggest construct replacements that are more idiomatic. So even if you didn’t know about lightweight clones, you’ll get suggestions to leverage them once they’re available for your codebase (and you can apply the vast majority trivially by asking clippy to do it automatically via --fix). This is distinctly not a superpower c++ has and why the complexity of c++ keeps piling up with either no one using the new features or using them in unnecessarily complicated ways.
> This is distinctly not a superpower c++ has and why the complexity
I guess you don't have that much experience with actual C++ development? Because there's a plethora of static analysis tools and any serious IDE come with refactoring tools on top of that, both assistive and automated, that will suggest fixes as you type. Rust didn't invent anything with clippy, however good the tool might be...
I worked for Coverity and people paid us ludicrous amounts of money to get the kinds of suggestions that rustc and clippy give everyone for free. I'm a huge fan.
It is much more complex than C, sure. But it's so much simpler than C++ and you won't find yourself digging through the reference, or standard, or whatever to understand the code you are writing. It's very close to the sweet spot of "not complex enough to make you bald, not simple enough to make your code complex" while also making correctness the default.
> Yet people seem to like making useful things in it so it must have gotten something right.
I'm not commenting on Rust, seriously! But I couldn't help to notice that this sentence is a non sequitur. Something right has been developed in PHP and Visual Basic, even in Javascript and Go; still, those developers who freely choose to use those abominations, they do deserve to be pointed at and made fun of.
In my opinion, it's not just about the complexity of C++ vs Rust. It's how often the complexity (or anything else) can unpleasantly surprise you, and how serious the consequences of that surprise are.
I don't think Rust is very complex. What you're doing is very complex. It's just that doing it in C/C++ lulls you into a false sense of security.
C++ is incredibly complicated. I mean there's a 278 page book just on initialization [1].
I have seen all sorts of bad multithreaded code that compilers have let someone write. It would've been much harder in Rust but Rust would've forced you to be correct. As an example, I've seen a pool of locks for message delivery where the locks are locked on one thread and unlocked on another. This would get deadlocked every now and again so every half second or so a separate process would just release all the locks.
Rust is more complex than C, yes, but as someone who has used both professionally, it is not even close to being as complex as C++. In fact it is closer in complexity to C than to C++.
Perhaps you can help guide a C expert but C++ avoider (and super-avoider of Rust, so far): If C is 1 in complexity, where does C++ and Rust fall. By 'complexity' here I mean: the ability to keep the Entire Language in your head at the same time. C, although it does have complex corners, is -- mostly -- not overly complicated. (As you can probably tell, I prefer assembly, because it is the least complicated. You can build your own abstractions up, which is the proper way to use assembly). Thank you for any insight; I'm not wedded to my views when shown a Better Way.
If C is 1 and C++ is 100 I would say Rust is like 25.
If your favorite language is assembly and C is too high-level for you then you are probably going to dislike Rust (and Java, Python, and every other modern language).
> The Rust for Linux project has been good for Rust
i just decided do a good ol' 'find -name "*.rs"' in the kernel tree to get a sense for what all this is about. from what i can tell, there's just an api compatibility layer (found in /rust) and then a smattering of proof of concept drivers in tree that appear to just be simple rewrites of existing drivers (with the exception of the incomplete nvidia thing) that aren't even really in use. from what i can tell even the android binder rust rewrite is vestigial.
the whole thing seems kinda cute but like, shouldn't this experiment in programming language co-development be taking place somewhere other than the source tree for the world's most important piece of software?
redox is a pretty cool experimental piece of software that might be the os of the future, why not do it there?
The gpu driver for Apple silicon is Rust and the author stated it would have been much more difficult to implement in C. It isn't upstreamed yet.
"""
Normally, when you write a brand new kernel driver as complicated as this one, trying to go from simple demo apps to a full desktop with multiple apps using the GPU concurrently ends up triggering all sorts of race conditions, memory leaks, use-after-free issues, and all kinds of badness.
But all that just… didn’t happen! I only had to fix a few logic bugs and one issue in the core of the memory management code, and then everything else just worked stably! Rust is truly magical! Its safety features mean that the design of the driver is guaranteed to be thread-safe and memory-safe as long as there are no issues in the few unsafe sections. It really guides you towards not just safe but good design.
"""
> the whole thing seems kinda cute but like, shouldn't this experiment in programming language co-development be taking place somewhere other than the source tree for the world's most important piece of software?
i don't really care for mindless appeals to authority. make your own arguments and defend them or don't bother.
this gpu driver looks pretty cool though. looks like there's much more to the rust compatibility layer in the asahi tree and it is pretty cool that they were able to ship so quickly. i'd be curious how kernel rust compares to user space rust with respect to bloat. (user rust is pretty bad in that regard, imo)
Mindless appeal to authority? I don't think that's how the fallacy really works. It's pretty much the authority that seems to disagree with your sentiment, that is if we can agree that Torvalds still knows what he's doing. Him not sharing your skepticism is a valid argument. The point being that instead of giving weight to our distant feelings, maybe we could just pause and be more curious as to why someone with much closer involvement would not share them. Why should we care more about the opinions of randos on hn?
To be fair, assigning the highly competent BDFL of Linux who has listened to a bunch of highly competent maintainers some credibility isn't mindless.
Unless you have a specific falsifiable claim that is being challenged or defended, it's not at all a fallacy to assume expert opinions are implicitly correct. It's just wisdom and good sense, even if it's not useful to the debate you want to have.
Not every mention of an authority's opinion needs to be interpreted as an "appeal to authority". In this case I think they're just trying to give you perspective, not use Torvalds opinion as words from god.
So rather than pointing to experts who're in the best position to know, you'd prefer bad rephrasing and airchair experts? Do you 'do your own research' too?
> Is rust going to synchronize shared memory access for me?
Much better than that. (safe) Rust is going to complain that you can't write the unsynchronized nonsense you were probably going to write, shortcutting the step where in production everything gets corrupted and you spend six months trying to reproduce and debug your mistake...
> aren't they just annotations? proper use of mutexes and lock ordering aren't that hard, they just require a little bit of discipline and consistency.
Spatial memory safety is easy, just check the bounds before indexing an array. Temporal memory safety is easy, just free memory only after you've finished using it, and not too early or too late. As you say, thread safety is easy.
Except we have loads of empirical evidence--from widespread failures of software--that it's not easy in practice. Especially in large codebases, remembering the remote conditions you need to uphold to maintain memory safety and thread safety can be difficult. I've written loads of code that created issues like "oops, I forgot to account for the possibility that someone might use this notification to immediately tell me to shut down."
What these annotations provide is a way to have the compiler bop you in the head when you accidentally screw something up, in the same way the compiler bops you in the head if you fucked up a type or the name of something. And my experience is that many people do go through a phase with the borrow checker where they complain about it being incorrect, only to later discover that it was correct, and the pattern they thought was safe wasn't.
Proper use of lock ordering is reasonably difficult in a large, deeply connected codebase like a kernel.
Rust has real improvements here, like this example from the fuschia team of enforcing lock ordering at compile time [0]. This is technically possible in C++ as well (see Alon Wolf's metaprogramming), but it's truly dark magic to do so.
The lifetimes it implements is the now unused lexical lifetimes of early Rust. Modern rust uses non-lexical lifetimes which accepts a larger amount of valid programs and the work on Polonius will further allow more legal programs that lexical lifetimes and non lexical lifetimes can’t allow. Additionally, the “borrow checker” they implement is RefCell which isn’t the Rust borrow checker at all but an escape hatch to do limited single-threaded borrow checking at runtime (which the library won’t notice if you use in multiple threads but Rust won’t let you).
Given how the committee works and the direction they insist on taking, C++ will never ever become a safe language.
Bit of a fun fact, but as one of the linked articles states the C++ committee doesn't seem to be a fan of stateful metaprogramming so its status is somewhat unclear. From Core Working Group issue 2118:
> Defining a friend function in a template, then referencing that function later provides a means of capturing and retrieving metaprogramming state. This technique is arcane and should be made ill-formed.
> Notes from the May, 2015 meeting:
> CWG agreed that such techniques should be ill-formed, although the mechanism for prohibiting them is as yet undetermined.
"Just" annotations... that are automatically added (in the vast majority of cases) and enforced by the compiler.
> proper use of mutexes and lock ordering aren't that hard, they just require a little bit of discipline and consistency.
Yes, like how avoiding type confusion/OOB/use-after-free/etc. "just require[s] a little bit of discipline and consistency"?
The point of offloading these kinds of things onto the compiler/language is precisely so that you have something watching your back if/when your discipline and consistency slips, especially when dealing with larger/more complex systems/teams. Most of us are only human, after all.
> how well does it all hold up when you have teamwork and everything isn't strictly adherent to one specific philosophy.
Again, part of the point is that Send/Sync are virtually always handled by the compiler, so teamwork and philosophy generally aren't in the picture in the first place. Consider it an extension of your "regular" strong static type system checks (e.g., can't pass object of type A to a function that expects an unrelated object of type B) to cross-thread concerns.
> aren't they just annotations? proper use of mutexes and lock ordering aren't that hard, they just require a little bit of discipline and consistency.
No, they are not. You also don't need mutex ordering as much since Mutexes in Rust are a container type. You can only get ahold of the inside value as a reference when calling the lock method.
> You also don't need mutex ordering as much since Mutexes in Rust are a container type. You can only get ahold of the inside value as a reference when calling the lock method.
Mutex as a container has no bearing on lock ordering problems (deadlock).
> What does rust have to do with thread safety and race conditions? Is rust going to synchronize shared memory access for me?
Rust’s strict ownership model enforces more correct handling of data that is shared or sent across threads.
> Speaking seriously, they surely meant data races, right? If so, what's preventing me from using C++ atomics to achieve the same thing?
C++ is not used in the Linux kernel.
You can write safe code in C++ or C if everything is attended to carefully and no mistakes are made by you or future maintainers who modify code. The benefit of Rust is that the compiler enforces it at a language level so you don’t have to rely on everyone touching the code avoiding mistakes or the disallowed behavior.
Rust's design eliminates data races completely. It also makes it much easier to write thread safe code from the start. Race conditions are possible but generally less of a thing compared to C++ (at least that's what I think).
Nothing is preventing you from writing correct C++ code. Rust is strictly less powerful (in terms of possible programs) than C++. The problem with C++ is that the easiest way to do anything is often the wrong way to do it. You might not even realize you are sharing a variable across threads and that it needs to be atomic.
> What does rust have to do with thread safety and race conditions? Is rust going to synchronize shared memory access for me?
Well, pretty close to that, actually! Rust will statically prevent you from accessing the same data from different threads concurrently without using a lock or atomic.
> what's preventing me from using C++ atomics to achieve the same thing
Now, is it okay to call `frobFoo` from multiple threads at once? Maybe, maybe not -- if it's not documented (or if you don't trust the documentation), you will have to read the entire implementation to answer that.
Now, is `frobFoo` okay to call from multiple threads at once? No, and the language will automatically make it impossible to do so.
If we had `&self` instead of `&mut self`, then it might be okay, you can discover whether it's okay by pure local reasoning (looking at the traits implemented by Foo, not the implementation), and if it's not then the language will again automatically prevent you from doing so (and also prevent the function from doing anything that would make it unsafe).
Rustlang doesn't aim to address race conditions. Sounds to me like overly "cautious" inefficient code you can write in any language. Think using `std::shared_ptr` for everything in C++, perchance…?
The comment probably refers to data races over memory access, which are prevented by usage of `Send` and `Sync` traits, rather than more general race conditions.
I see, but that's not the point of my comment. I don't know rustlang, perhaps I could address that if someone translated the rust-specific parlance to more generally accepted terms.
I'm not sure I understand the point of your comment at all.
Rust does, successfully, guarantee the lack of data races. It also guarantees the lack of memory-unsafety resulting from race conditions in general (which to be fair largely just means "it guarantees a lack of data races", though it does also include things like "race conditions won't result in a use after free or an out of bounds memory access").
If by address it you mean "show how C/C++ does this"... they don't and this is well known.
If by address it you mean "prove that rust doesn't do what it says it does"... as that point you're inviting someone to teach you the details of how rust works down to the nitty gritty in an HN comment. You'd be much better off finding and reading the relevant materials on the internet than someones off hand attempt at recreating them on HN.
The point of my comment is that in my experience, incompetently written, overly-cautious code tends to be more safe at the expense of maintainability and/or performance.
Sadly, I don't know rustlang, so I can't tell if the inability to describe its features in more commonly used terms is due to incompetence or the features being irrelevant to this discussion (see the title of the thread).
The thing is you aren't really asking about a "feature" of rust (as the word is used in the title of the thread), unless that feature is "the absence of data races" or "memory safety" which I think are both well defined terms† and which rust has. Rather you're asking how those features were implemented, and the answer is through a coherent design across all the different features of rust that maintains the properties.
As near as I can tell to give you the answer you're looking for I'd have to explain the majority of rust to you. How traits work, and auto traits, and unsafe trait impls, and ownership, and the borrow checker, and for it to make sense as a practical thing interior mutability, and then I could point you at the standard library concepts of Send and Sync which someone mentioned above and they would actually make sense, and then I could give some examples of how everything comes together to enable memory safe, efficient, and ergonomic, threading primitives.
But this would no longer be a discussion about a rust language feature, but a tutorial on rust in general. Because to properly understand how the primitives that allow rust to build safe abstractions work, you need to understand most of rust.
Send and Sync (mentioned up thread) while being useful search terms, are some of the last things in a reasonable rust curriculum, not the first. I could quickly explain them to someone who already knew rust, and hadn't used them (or threads) at all, because they're simple once you have the foundation of "how the rest of rust works". Skipping the foundation doesn't make sense.
† "Memory safety" was admittedly possibly popularized by rust, but is equivalent to "the absence of undefined behaviour" which should be understandable to any C programmer.
> The point of my comment is that in my experience, incompetently written, overly-cautious code tends to be more safe at the expense of maintainability and/or performance
Well, yes, but that's the whole value of Rust: you don't need to use these overly-cautious defensive constructs, (at least not to prevent data races), because the language prevents them for you automatically.
Safe Rust does. To the extend Rust interfaces that wrap kernel APIs will achieve safety for the drivers that make use of them remains to be seen. I think it will indeed do this to some degree, but I have some doubts whether the effort and overhead is worth it. IMHO all these resources would better be invested elsewhere.
Thats kinda the problem, there are concepts in rust that don't have equivalents in other common languages. In this case, rust's type system models data-race-safety: it prevents data races at compile time in a way unlike what you can do in c or c++. It will prevent getting mutable access (with a compile time error) to a value across threads unless that access is syncronized (atomics, locks, etc)
And from what I can see, rustlang mutability is also a type system construct? I.e. it assumes that all other code is Rust for the purpose of those checks?
> rustlang mutability is also a type system construct?
Yes
> I.e. it assumes that all other code is Rust for the purpose of those checks?
Not exactly, it merely assumes that you upheld the documented invariants when you wrote code to call/be-called-from other languages. For example that if I have a `extern "C" fn foo(x: &mut i32)` that
- x points to a properly aligned properly allocated i32 (not to null, not to the middle of un-unallocated page somewhere)
- The only way that memory will be accessed for the duration of the call to `foo` is via `x`. Which is to say that other parts of the system won't be writing to `x` or making assumptions about what value is stored in its memory until the function call returns (rust is, in principle, permitted to store some temporary value in `x`s memory even if the code never touches x beyond being passed it. So long as when `foo` returns the memory contains what it is supposed to). Note that this implies that a pointer to the same memory isn't also being passed to rust some other way (e.g. through a static which doesn't have a locked lock around it)
- foo will be called via the standard "C" calling convention (on x86_64 linux this for instance means that the stack pointer must be 2-byte aligned. Which is the type of constraint that is very easy to violate from assembly and next to impossible to violate from C code).
That it's up to the programmer to verify the invariants is why FFI code is considered "unsafe" in rust - programmer error can result in unsoundness. But if you, the programmer, are confident you have upheld the invariants you still get the guarantees about the broader system.
Rust is generally all about local reasoning. It doesn't actually care very much what the rest of the system is, so long as it called us following the agreed upon contract. It just has a much more explicit definition of what that contract is then C.
Also we can (in 2024 Edition) say we're vouching for an FFI function as safe to call, avoiding the need for a thin safe Rust wrapper which just passes through. We do still need the unsafe keyword to introduce the FFI function name, but by marking it safe all the actual callers don't care it wasn't written in Rust.
This is fairly narrow, often C functions for example aren't actually safe, for example they take a pointer and it must be valid, that's not inherently safe, or they have requirements about the relative values of parameters or the state of the wider system which can't be checked by the Rust, again unsafe. But there are cases where this affordance is a nice improvement.
Also "safe" and "unsafe" have very specific meanings, not the more widely used meanings. It's not inherently dangerous to call unsafe code that is well written, it's really more a statement about who is taking responsibility for the behavior, the writer or the compiler.
I like the term "checked" and "unchecked" better but not enough to actually lobby to change them, and as a term of art they're fine.
Yes. Just like C++ "const" is a type system construct that assumes all other code is C++ (or at least cooperates with the C++ code by not going around changing random bytes).
As far as I can tell, ANY guarantee provided by ANY language is "just a language construct" that fails if we assume there is other code executing which is ill-behaved.
a data race is specific kind of race condition; it's not rust parlance, but that specificity comes up a lot in rust discussions because that's part of the value
> since Rust is not the only language susceptible to data races.
The point is rather that it’s not. The “trait send sync things” specify whether a value of the type is allowed to be respectively move or borrowed across thread boundaries.
I mean, reliably tracking ownership and therefore knowing that e.g. an aliased write must complete before a read is surely helpful?
It won't prevent all races, but it might help avoid mistakes in a few of em. And concurrency is such a pain; any such machine-checked guarantees are probably nice to have to those dealing with em - caveat being that I'm not such a person.
Heh. This is such a C++ thing to say: “I want to do the right thing, but then my code is slow.” I know, I used to write video games in C++. So I feel your pain.
I can only tell you: open your mind. Is Rust just a fad? The latest cool new shiny, espoused only by amateurs who don’t have a real job? Or is it something radically different? Go dig into Rust. Compile it down to assembly and see what it generates. Get frustrated by the borrow checker rules until you have the epiphany. Write some unsafe code and learn what “unsafe” really means. Form your own opinion.
The point of the project is not meant to be an experiment in "programming language co-development", the point of the project is to use Rust in Linux kernel development. The project was started by Linux kernel developers who want to use Rust. It's been a slow start, but as you say, it's the world's most important piece of software so progress has been careful and trying to get the foundations right before going crazy with it.
The fact that Rust gets to benefit from the project too is just an added bonus.
> from what i can tell even the android binder rust rewrite is vestigial.
Vestigal how? The commit message in the version in Linus's tree (from commit eafedbc7c050c44744fbdf80bdf3315e860b7513 "rust_binder: add Rust Binder driver") makes it seem rather more complete:
> Rust binder passes all tests that validate the correctness of Binder in
the Android Open Source Project. We can boot a device, and run a variety
of apps and functionality without issues. We have performed this both on
the Cuttlefish Android emulator device, and on a Pixel 6 Pro.
> As for feature parity, Rust binder currently implements all features
that C binder supports, with the exception of some debugging facilities.
The missing debugging facilities will be added before we submit the Rust
implementation upstream.
----
> shouldn't this experiment in programming language co-development be taking place somewhere other than the source tree for the world's most important piece of software?
Rust for Linux did start as an out-of-tree project. The thing is that no matter how much work you do out of tree if you're serious about trying integration out you'll have to pull in experimental support at some point - which is more or less what is happening now.
Before you can have complex drivers you need the interface layer that the drivers are built on. The RfL project works on that, upstreaming more infrastructure work until there's enough to submit a complex driver. Redhat is working on nova, asahi on the apple GPU, collabora is working on one for ARM Mali.
If 3 GPU drivers don't count as complex, real drivers then what does?
"Experiment" is a misnomer. Rust has been around long enough and demonstrated more than enough real advantages in writing reliable code that we know it's what we want to be doing.
It's been moving slowly at first because you need a lot of bindings done before you can do interesting work, and bindings/FFI tend to be fiddly, error prone things that you want to take your time on and get right - that's where you deal with the impedance mismatch between C and Rust and have to get all the implicit rules expressed in the type system (if you can).
It'll go faster once all the bindings are in place and people have more experience with this stuff. I've been greatly looking forward to expanding bcachefs's use of rust, right now it's just in userspace but I've got some initial bindings for bcachefs's core btree API.
Real iterators, closures, better data types, all that stuff is going to be so nice when it can replace pages and pages of macro madness.
In discussions like this, I sometimes feel that the importance of related work like the increasing use of Rust in Android and MS land is under-appreciated. Those who think C is fine often (it seems to me) make arguments along the lines that C just needs to have a less UB-prone variant along the lines of John Regehr and colleagues' "Friendly C" proposal,[0] which unfortunately Regehr about a year and a half later concluded couldn't really be landed by a consensus approach.[1] But he does suggest a way forwards: "an influential group such as the Android team could create a friendly C dialect and use it to build the C code (or at least the security-sensitive C code) in their project", which is what I would argue is happening; it's just that rather than nailing down a better C, several important efforts are all deciding that Rust is the way forward.
The avalanche has already started. It is too late for the pebbles to vote.
> This post is a long-winded way of saying that I lost faith in my ability to push the work forward.
The gem of despair:
> Another example is what should be done when a 32-bit integer is shifted by 32 places (this is undefined behavior in C and C++). Stephen Canon pointed out on twitter that there are many programs typically compiled for ARM that would fail if this produced something besides 0, and there are also many programs typically compiled for x86 that would fail when this evaluates to something other than the original value.
Some parts of the industry with a lot of money and influence decided this is the way forward. IMHO Rust has the same issue as C++: it is too complex and a memory safe C would be far more useful. It is sad that not more resources are invested into this.
I'm entirely unconvinced that a low-level† memory safe C that is meaningfully simpler than rust is even possible, let alone desirable. IMHO Basically all of rust's complexity comes from implementing the structure necessary to make it memory safe without making it too difficult to use††.
Even if it is though, we don't have it. It seems like linux should go with the solution we have in hand and can see works, not a solution that hasn't been developed or proved possible and practical.
Nor is memory safety the only thing rust brings to the table, it's also brings a more expressive type system that prevents other mistakes (just not as categorically) and lets you program faster. Supposing we got this memory safe C that somehow avoided this complexity... I don't think I'd even want to use it over the more expressive memory safe language that also brings other benefits.
† A memory-safe managed C is possible of course (see https://fil-c.org/), but it seems unsuitable for a kernel.
†† There are some other alternatives to the choices rust made, but not meaningfully less complex. Separately you could ditch the complexity of async I guess, but you can also just use rust as if async didn't exist, it's a purely value added feature. There's likely one or two other similar examples though they don't immediately come to mind.
I don't think so. First, Rust did not come from nowhere, there were memory safe C variants before it that stayed closer to C. Second, I do not even believe that memory safety is that important that this trumps other considerations, e.g. the complexity of having two languages in the kernel (even if you ignore the complexity of Rust). Now, it is not my decision but Google's and other company's influence. But I still think it is a mistake and highlights more the influence of certain tech companies on open source than anything else.
> First, Rust did not come from nowhere, there were memory safe C variants before it that stayed closer to C.
Can you give an example? One that remained a low level language, and remained ergonomic enough for practical use?
> Second, I do not even believe that memory safety is that important that this trumps other considerations
In your previous comment you stated "a memory safe C would be far more useful. It is sad that not more resources are invested into this". It seems to me that after suggesting that people should stop working on what they are working on and work on memory safe C instead you ought to be prepared to defend the concept of a memory safe C. Not to simply back away from memory safety being a useful concept in the first place.
I'm not particularly interested in debating the merits of memory safety with you, I entered this discussion upon the assumption that you had conceded them.
> Can you give an example? One that remained a low level language, and remained ergonomic enough for practical use?
They can't, of course, because there was no such language. Some people for whatever reason struggle to acknowledge that (1) Rust was not just the synthesis of existing ideas (the borrow checker was novel, and aspects of its thread safety story like Send and Sync were also AFAIK not found in the literature), and (2) to the extent that it was the synthesis of existing ideas, a number of these were locked away in languages that were not even close to being ready for industry adoption. There was no other Rust alternative (that genuinely aimed to replace C++ for all use cases, not just supplement it) just on the horizon or something around the time of Rust 1.0's release. Pretty much all the oxygen in the room for developing such a language has gone to Rust for well over a decade now, and that's why it's in the Linux kernel and [insert your pet language here] is not.
BTW, this is also why people being are incentivized to figure out ways to solve complex cases like Rcu-projection through extensible mechanisms (like the generic field projection proposal) rather than ditching Rust as a language because it can't currently handle these ergonomically. The lack of alternatives to Rust is a big driving factor for people to find these abstractions. Conversely, having the weight of the Linux kernel behind these feature requests (instead of e.g. some random hobbyist) makes it far more likely for them to actually get into the language.
It's hard to view the relatively small scope of existing Rust code in the kernel at the present time as an indictment of the utility of Rust being used in the kernel when there are major kernel maintainers who have publicly stated that they have been doing everything in their power to block any amount of Rust code from getting merged into any part of the codebase.
> shouldn't this experiment in programming language co-development be taking place somewhere other than the source tree for the world's most important piece of software?
The language co-development isn't unique to Rust. There are plenty of features in GCC and Clang that exist specifically for Kernel usage too.
> redox is a pretty cool experimental piece of software that might be the os of the future, why not do it there?
Because people want to use Rust where they use C, right now? Whereas yours is a perfectly fine criticism, it ignores that people want the good stuff, everywhere, in the things they actually use every day. And since this is something the project lead wants to do, this doesn't seem to problem/live issue.
> Since the talks described in this article, the work on field projection has received an update. Lossin wrote in to inform LWN that all fields of all structures are now considered structurally pinned, so projecting a Pin will now always produce a Pin<&mut Field> or similar value.
Huh, I missed that part. It's a pretty technical point, but I'm happy they made the decision, it held up a lot of discussions.
> The final design, taking inspiration from C++, would be a form of guaranteed optimization, where constructing a new value and then immediately moving it to the heap causes it to be constructed on the heap in the first place.
Note that there's some discussion about the name of that proposal, because "optimization" gives the wrong idea (that it's optional or could depend on the backend).
I'm probably misunderstanding the complexity of the problem, but wouldn't this be solvable by just defining the right calling convention?
"Any structures larger than x, or any structures marked with a marker type, are returned by the caller providing an outref to a buffer with correct size, and the callee directly writes the structure into that buffer."
Then you could just write normal code like
fn initialize() -> A {
// the initialization code
}
and it would just work? And it would reduce unnecessary copies in a lot of other situations too?
Given how much work has been put into this issue, and how much less convenient the proposed solutions are, I feel like I must be missing something.
The missing piece is that it would still force you to make even larger types in many cases, such as `Result<Large, Error>`.
Essentially the problem is composability. If you are building a large type from a sequence of other large types, and one or more step is fallible, the normal return ABI breaks down very quickly.
Because constructors are really weird. Usually in Rust, when a struct is constructed, it already upholds all its invariants because construction is the "last" step in the initialization function. But with a C++-like constructor, it starts with a struct where all fields are in an invalid state, and then the struct's invariants are slowly established field by field. This is kinda impossible to square with Rust's safety promise. Even in safe languages like Java, there are often bugs when one calls other function from the constructor, that now observes the instance under construction violating its usual invariants. And this is also something Rust wants to avoid.
> But with a C++-like constructor, it starts with a struct where all fields are in an invalid state, and then the struct's invariants are slowly established field by field.
AIUI, that's why MaybeUninit<T> exists. But even if you address the issue of it being unsafe to assert that a MaybeUninit has been initialized (which &out references could in principle solve) there are real problems with this; for example, MaybeUninit<T> has no niches or free-for-use padding even when T does, so you can't just "project" MaybeUninit to individual fields except in special cases. My understanding is that C++ partial initialization has the exact same issues in principle, they just don't come up as often because the standard for code correctness is a lot less rigorous.
These seem like the first features that Rust in Linux bring to the Rust language that are not almost exclusively useful to the kernel. In my perception the focus on bringing features for the kernel has held up development in other parts of the language and the standard library.
As I understand it systems programming is the priority application area for Rust, and there are plenty of projects working on OSs, embedded or other bare-metal cases, as well as interoperability with complex C codebases.
At first glance, these features look quite general to me and not particularly tied to the kernel, they are important utilities for doing this kind of programming in the real world.
C interop is excellent and has been for years. The one piece that still needs unstable is defining/exposing varargs functions (support for calling them was stabilized many years ago). You can write almost anything you can write in C in (partly unsafe) Rust, in fact there are projects like c2rust that automate this translation.
These new features are all about making things that the kernel devs need possible in safe Rust. This often requires support for some quite fancy abstractions, some of which cannot be expressed in current stable Rust.
Only if you primarily work with `cargo` and want to interact with C from Rust. The other way around has far less support and `rustc` does not standardize the object generation. This is actively preventing projects like `systemd` to adopt Rust into their project as an example.
> Only if you primarily work with `cargo` and want to interact with C from Rust.
In what way(s) does Rust's C interop depend on cargo?
> The other way around has far less support and `rustc` does not standardize the object generation.
I believe in this context the understanding is that you're going to be using `extern "C"` and/or `#[repr(C)]` in your Rust code, which gives you a plain C interface. I think attempting to use "raw" Rust code from other languages is a rare phenomenon, if it's even attempted at all.
> This is actively preventing projects like `systemd` to adopt Rust into their project as an example.
Could you point out specific instances from that thread? From a quick glance I didn't see any obvious instances of someone saying that using Rust from C is problematic.
> In what way(s) does Rust's C interop depend on cargo?
Do rust and cargo allow for multiple interpretations of the same C header file across different objects in the same program? That's how C libraries are often implemented in practice due to preprocessor tricks, though I wish it wasn't normal to do this sort of thing.
I would expect so. Rust and Cargo don't consume C header files directly at all. They consume bindings generated by bindgen (or hand written if you prefer). So you could probably generate mulitple bindings if you needed multiple interpretations of a C header.
If the header files are consumed by C code that is then consumed by Rust then you'll have full support for what C supports because it will be compiled by a C compiler.
Rust and Cargo do not build C programs, so, the answer to that is "no", strictly speaking.
However, some people use cargo's build scripts to build c programs, which then you can link into your Rust program. Support would then depend on whatever the person wrote with the script, which in my experience usually delegates to whatever build system that project uses. So it should work fine.
I have to disagree here a little bit. Calling C functions from Rust is a very pleasant experience, but the other way around is not so nice. You usually have to manually create types that will unpack rust collections into C compatible structures (think decomposing `Vec` into ptr, len, capacity) & then ensure that memory passed between the two sides is free'd with the appropriate allocator. Even with `cbindgen` taking care of the mindless conversions for you, you still have to put a lot of thought into the API between the two languages.
I am currently working on a fairly involved C & Rust embedded systems project and getting the inter-language interface stable and memory-leak free took a good amount of effort. It probably didn't help that I don't have access to valgrind or gdb on this platform.
I feel this might come down to the scope one has in mind when thinking of the word "interop". I think one can reasonably simultaneously claim that the interop "mechanics" are excellent in that it's generally possible to create a Rust library that quacks like a C library and that basically any C library is usable by Rust code, but the interop "ergonomics" are suboptimal in that (as you say) actually writing the glue code can be a bit of an adventure.
I think that's a fair assessment. To your point `cbindgen` makes the mechanics of the whole thing painless & linking was trivial. That's worth a lot especially when compared to other languages.
Writing Rust code to be called from C (but within the same application)? Doable but somewhat painful.
Writing Rust code to act like a C shared library? Quite painful and some pretty important features are missing (proper symbol versioning support being the most obvious one). Theoretically doable if you're willing to compromise.
There's also some aspects of FFI-safety that are very subtle and easy to mess up:
* #[repr(C)] enums still have the same requirements as Rust enums and so C callers can easily trigger UB, so you need to use something like open_enum. Thankfully cbindgen is too dumb to know that #[open_enum] is a proc macro and produces a non-enum type.
* Before io_safety in Rust 1.63, dealing with file descriptors from C without accidentally closing them was horrific (though this was a wider problem in Rust). BorrowedFd is quite nice -- though Rustix will panic if you use negative fds and so you need to add validation and your own type in practice. However, #[repr(transparent)] is very nice for this.
* Lots of reading about unsafe Rust is necessary when doing most non-trivial things with C FFI.
* You need to make use of a lot of compiler internals, build scripts, and other magic to get the output you want.
* Tools like cargo-c and cbindgen are nice and probably work great for 80% of projects, but the 20% really suffer from no useful tooling. I haven't tried to use rustc directly to work around some of the remaining issues, but I suspect it'd be even more painful.
I would say that the C interop with Rust is pretty good but it has lots of room for improvement and it feels like very few resources have been spent on it after they got the core stuff working.
Source: I've been writing a Rust library intended to be used primarily via C FFI and run into a lot of issues...
I see Rust's place on low level systems programming, for everything else on userspace compiled managed languages are a much better option, systems following an architecture like Self, Inferno or Android, so I don't see a big deal with these efforts focusing on low level C like capabilities.
> for everything else on userspace compiled managed languages are a much better option
As someone who's written a number of userspace applications in many languages as well as embedded firmwares running on bare metal, Rust is a rare gem that excels at both.
Well https://github.com/timschmidt/egui-rad-builder has come together rather well in the last week of hacking, if I say so myself. I think building a similar app with QT, for example, would have been significantly more challenging.
I'm particularly fond of how easy it was to make all the controls live in the editor, and editable with changes appearing immediately. imgui would probably provide a similar experience, but I find C++ much more of a pain to work with than Rust.
I looked at Slint a couple years ago when I was evaluating UI toolkits. Looks slick! The only thing that turned me off was needing an additional DSL to define UIs. Trying to learn fewer languages, more deeply, these days. Is it possible to use Slint without the DSL?
Slint does require using its DSL to define UIs, but I'd argue it's not really like learning a whole new language. It's not harder than learning the API surface of any other GUI framework.
I don't think the DSL is any good. It looks weird (like a mix of CSS and classes), and it has a side effect of locking the user to a specific product.
In your article, you mention that an imperative code in Rust looks more complicated, but this can be fixed by adding an "object tree" syntax to Rust, that allows creating trees of objects and link them like this:
This syntax could be used not only for UI, but for describing configuration, database tables and many other things. I think it would be better solution than a proprietary language.
Also I think it would be better if GUI could be drawn in the editor, it would allow to use lower paid developers without expensive computer science education for making UI.
I appreciate the answer, and you taking time to write up your thoughts on it. Your point about the GUI builder working with the DSL is noted. In the egui RAD builder, I am manipulating structs which describe the UI (and which can be exported / imported as JSON) prior to code generation, which amounts to something similar. I still feel I have to context switch less, the back-and-forth is of more concern to me than learning an additional language.
That said, great work! There's plenty of room in the language for more than one solution!
That’s a compelling post. You drew me in by calling SQL a DSL. You’re right, but if you asked me to list the DSLs I use regularly I never would have thought of SQL until now. My thinking was too uptight because I didn’t consider the “domain” in DSL could be something as big as querying structured data. That makes me wonder if Rust is a DSL for the domain of general computing (obviously not in the usual intended meaning of DSL).
Like the person you’re replying to, I am generally averse to DSLs where the domain is a single project because I associate them with previous bad experiences where I spent time learning only to find it totally misses the mark. There’s also the issue of software where I’m the sole maintainer and look at it rarely. If it’s the only place I use Slint, then I’m going to be worried that I need to relearn the DSL in three months when I want to tweak the UI. Although you can probably say the same about any non-trivial framework’s API whether or not it uses a DSL.
All that said, I’ll be a bit more open to DSLs after reading your post, and if I ever need to write a GUI in Rust I’ll give Slint a shot (although that seems unlikely since I don’t typically write Rust or GUIs).
But that’s no longer the choice you need to make. Ubuntu themselves have said for a couple of years now that every new GUI app they make natively for Linux is going to be Flutter and dedicated a bunch of engineers to the project to make sure it’s a first class citizen.
Beyond that, Dart / Futter are truly an absolute pleasure to use for that use case.
Sounds great for the Dart people. But unrelated to correcting misinformation about Rust. I'm not opposed to whatever language folks want to use. Just out here hacking in Rust because it suits my particular needs. And finding that it's enjoyable to hack GUIs as well as firmwares in.
No I get all that, but to the parent comments point however, there’s no way in hell I would ever pretend for a moment that one is as straightforward as the other and it’s no longer a choice between Rust and C++ both of which add a lot of complexity to a scenario where it offers very little benefit.
Over the years, I've found that the complexity is there whether or not it's been papered over by a runtime. It's wonderful to forget about, until it's not. Some applications may never run into the limitations imposed by abstracting it away, but others run into them quickly.
"There ain't no such thing as a free lunch" as the saying goes.
To which the counter argument I guess would be.. there’s no sense in trying to solve for some abstract future set of problems that the vast majority of people are never going to have ahead of time on the off chance you’re one of them.
That too requires a substantial investment of time and resources.
I think in a more pragmatic sense too that you can form a very good understanding on if you’re going to have weird special requirements just from looking at what others have done with the same tools in similar situations before you.
> there’s no sense in trying to solve for some abstract future set of problems that the vast majority of people are never going to have
> That too requires a substantial investment of time and resources.
The discussion has gotten to be pretty abstract at this point. To get back to concrete examples, the egui RAD builder I've been hacking on worked on day 1, first commit. It's been a joy to put together, and no more difficult than building GUI apps with any other toolkit I've worked with. Which causes me to question your statements about additional complexity. You can dig deep and do dark magic with Rust if you want, but you can also treat it like any high level language and get things done quickly. That's part of what makes it a rare gem to me.
Some folks don't like dealing with strict types, or with the borrow checker, but I find that the errors they illuminate for me would have been similarly serious in other languages which lacked the tooling to highlight them. Which adds to my appreciation of Rust.
One week of hacking makes every library look good, but have you shipped actual product that other people widely use with Rust GUI yet? What was the experience there and - most importantly - what were the downsides?
I've been working with egui for a couple years. Written maybe a dozen applications with it doing various things from image manipulation to 3D graphics. As I've said elsewhere in the thread, I haven't run into anything I wasn't able to accomplish with it.
Immediate mode has it's detractors, but in practice I've found it remarkably flexible, the resulting code relatively clean, and egui gets out of the way when I want to do something like blit fast graphics to a memory mapped area of the screen. Responses have been positive.
BTW, this happens to almost all languages. Which ACTUAL good GUIs toolkits exist? And which ACTUAL languages HAVE good integration or implementation of them?
A good GUI kit AND integration is a bigger task than do a Os or a RDBMS. (And neither are many good languages for RDBMS)
I think Swift (and even ObjC) is perfect for AppKit & UIKit. I think those frameworks are pretty good and I like using them. Languages have great integration, Swift literally made around them. Those toolkits have great integrations with the macOS.
I find C# a pretty nice language for GUI, I assume it has good (maybe not great) integration with at least one of MS GUI toolkits.
I find Rust good for GUI, but right now story is meh. Wrapper style frameworks always suffer from Rust not being whatever it wraps. Pure Rust framework miss a lot of features compared to wrapper frameworks.
I strongly agree with this. In particular the Rust GUI libraries I've looked at have text layout / rendering that is nowhere near what should be considered adequate today (imo).
For example egui:
- It doesn't do any bidi reordering.
- It also doesn't do any font fallback so even if you didn't need bidi you can't render many languages without first acquiring an appropriate font somehow.
- Complex shaping is nowhere to be seen either.
- egui's italics look really terrible and I'm not sure why since I can't believe even synthesized ones have to look that bad.
CSS has been doing this for years and it's been doing it very well. So I am kind of disappointed that we don't have equally powerful tools in the general Rust ecosystem. Even just considering text layout / rendering libraries, only `cosmic-text` has an API that is somewhat in the right direction[1], but even it fails simply because I don't see a way to insert a button (block element) in-between the text[2].
Note that I'm not just hating on egui here, egui is amazing. Afaict it is the most complete GUI library for Rust and it's great to be able to make GUIs this easily. However I can't just not point out that it is, in fact, not perfect and doesn't truly "excel" at GUIs.
Also I have no idea how inline layout looks in other established GUI libraries like GTK and Qt so maybe I'm complaining about something that is not available most places outside a browser. If anyone knows, it would be interesting to learn how well they compare here.
[1] CSS inline layout is so complex that even this is a decent achievement, since it is not reasonable to expect most CSS features out of new Rust libraries with a significant time disadvantage.
[2] This is non-trivial because bidi reordering should happen on the whole paragraph not only on both sides of the button, so the inline layout API must handle non-text blocks in text.
This is a detail oriented list of real issues, which is wonderful! ChatGPT found open issues in the egui Github for each of your problems, so they're known about and being worked on.
However, it seems like most of the other UI toolkits discussed here, even in other languages, suffer similar issues. Which points to the difficulty of the problems. Consequently, I don't have any problem praising egui and Rust alongside. They're great! And even great can always get better! :)
Indeed. Last I knew all the Rust UI libraries were declarative and/or immediate mode, both of which have their place but I’m not convinced that they’re suitable in all situations. Sometimes you need a boring “old style” imperative UI framework with deep toolbox of capable widgets (think AppKit, win32, etc), but that’s notably absent in the Rust world.
https://www.gpui.rs/ is a relatively new entrant which is described as "hybrid immediate and retained mode". Maybe worth checking out for your use cases.
Immediate mode was certainly a different paradigm to wrap my head around, but so far I haven't found anything I couldn't accomplish with egui, including some fairly complex applications like https://timschmidt.github.io/alumina-interface/
From a quick glance, about as far as the other Rust toolkits. Looks declarative with with a relatively small number of more basic widgets (a lot of custom widget code is necessary), as opposed to e.g. AppKit which is imperative and comes with a great number of rich widgets that are ready to use out of the box.
Sure, but that's true of basically every language except maybe C++, C#, Javascript/Typescript and Dart.
GUIs are incredibly hard and most languages never get high quality GUI libraries. Rust is still pretty young and a ton of people are working on the problem so that will definitely change.
Fearless concurrency, is only fearless for a very fine grained version of it.
In memory resources shared among threads.
Turns out threads also may share resources like out-of-process files, memory mapped regions, shared memory, databases, distributed transactions,.... where the Send and Sync traits is of no help.
Also you happen to forget Haskell has a Software Transactional Memory, and Swift also has similar protocols since version 6, and effects are a thing in OCaml, Scala, Koka, while languages like Dafny, Koka and Idris also provide similar capabilities via proofs.
That's why everybody who cares about OS safety should invest into capability-based operating systems. They take the concept of Send and Sync and implement at the runtime of the entire computer.
> for everything else on userspace compiled managed languages are a much better option
That might be true if you're developing a pure application [1], but not if you're writing a library. Have fun integrating a C# library in a Java program, a Java library in a C# program, or either of those in a Python, Node, C, C++, or Rust program. There's something to be said for not requiring a runtime.
[1] Unless you care about easy parallelization with statically guaranteed thread-safety. But hey, who needs this in the age of multicore CPUs?
Message-passing IPC is much, much slower and less efficient than shared-memory communication, and inter-process IPC (both message-passing and shared-memory) is much less convenient than intra-process multi-threading. Rust is the only mainstream language, managed or otherwise, which enables safe and efficient multi-threading.
Not at all, because as I explain on another sibiling answer, it is only safe if we cut down the use cases to a very specific one that helps to sell that narrative.
Eh, modern OS-es indeed have loads of problems still, of which non-transactional access to the filesystem is in my top three, but to put those problems on Rust is a bit uncharitable. It's not Rust's fault that zero kernel devs have the courage to finally start enforcing patterns that have proven themselves useful and safe for decades, in other areas outside of IT as well (i.e. in financial accounting).
Rust plucks the fruit it can reach and it mostly stays in its lane and it's trying to expand it here and there (like Rust in Linux and embedded). I too want one ultimate language and one ultimate kernel but I don't think you and I will live to see it. Maybe our grandchildren will not as well.
aren't really relevant, and Rust directly helps with the other two aspects:
- memory mapped regions
- shared memory
In practice, your "very specific" aspect is the most important one, and the hardest to get right without Rust's Send and Sync traits and their automatic enforcement.
Who gets to say what is relevant is the architect driving the project implementation.
> aren't really relevant, and Rust directly helps with the other two aspects:
Not at all, because Rust code has nothing to say about what other processes do to those resources, the only thing you can do is wrap accesses in a unsafe code block and hope for the best, that nothing was corrupted.
I think you're confusing concurrency with parallelism. I've been talking about parallelization since my first comment in this thread. There's some overlap, yes, but the aspects you listed have typically very little to do with parallelization, which is why I called them "[not] really relevant". And where they do matter to multi-threading (the shared memory part), Rust does help with correctness.
Everytime features are mentioned it makes me go: "it's all fun and games until someone puts tokio into the kernel", better yet if rust becomes complete enough and someone makes a direct composition renderer we could have entire applications that run entirely in the kernel which could be... interesting.
I was about to take offence at the use of “trivial” in this context. But then I noticed your handle, lol. You have the license to say that, thanks for your contributions!
All these features sound really awesome and would also benefit many non-kernel cases (especially generalized projections). Very happy to see Linux driving the language forward.
LLMs seem generally unsuited for the task, because they're fundamentally approximators that won't always get things right, and as a result will introduce subtle bugs. Perhaps if you paired them with some sort of formal methods... I'm not aware of anyone doing that. Tests aren't sufficient - lots of subtle bugs will not be caught by existing test suites.
fn project_reference(r: &MyStruct) -> &Field {
&r.field
}
unsafe fn project_pointer(r: *mut MyStruct) -> *mut Field {
unsafe { &raw mut (*r).field }
}
// The equivalent C code would look like this:
struct field *project(struct my *r) {
return &(r->field);
}
I am a very heavy Rust user. I mostly program in safe Rust while occassionally dipping into unsafe Rust.
IDK, I think Rust should stick with what it is good at and not try to expand into domain that it is clearly not nicely designed for. That is, what if the best way to implement linked list in RUST is via an array of indices and NOT through RefCell or whatever it is? What if Rust will never ever have a sane way to implement linked list. What is so wrong with that? I think there should be a very clean divide between C and Rust. Rust stays in the happy Rust world and C stays on the happy C world.
I am not sure I am excited to see something like this
The best way to implement a linked list is with unsafe in a collection type. You write that type once, check that it’s bullet proof, and then go onto the next thing. I’ve got a sorted map using doubly linked list and I don’t think twice about it. Using an array for a linked list means the compiler can’t tell if you are being unsafe. You still have all the same problems.
Looking at the rust rfc for the lightweight clones feature [1]. It took me a while to sort of understand it. Once I did I was excited for the feature but after awhile I was once again struck by the observation that rust is a very complex language to learn.
To me as someone who's not learned either it looks like all the concepts and features of 'true' modern C++ (as opposed to C + a few extra features) spliced with the features and concepts of Haskell.
Yet people seem to like making useful things in it so it must have gotten something right. So I'll probably get around to attempting to really use it again.
[1]: https://github.com/joshtriplett/rfcs/blob/use/text/3680-use....
In my experience, C++ is a much more complicated language. The 8 ways to initialize something, the 5 types of values (xvalues etc.), inconsistent formatting conventions, inconsistent naming conventions, the rule of 5, exceptions, always remembering to check `this != other` when doing a move assignment operator, perfect forwarding, SFINAE, workarounds for not having a great equivalent to traits, etc. . Part of knowing the language is also knowing the conventions on top that are necessary in order to write it more safely and faster (if your move constructor is not noexcept it'll cause copies to occur when growing a vector of that object), and learning the many non-ideal competing ways that people do things, like error handling.
Why check this != other? I've seen this once before in a codebase and concluded it was unnecessary.
Asking as someone whose life became much easier after opting not do anything of the above and just write C in C++ ;-)
Consider a move assign to a vector<int> from src to this. The first step is for this to free its resources, the second step is to assign src resources to this, the third step is to set src resources to null.
If src and this are equal and you don't check for it, then you end up destroying src/this resources and the end result would be an empty vector (since the last step is to clear everything out).
The expected behavior is a no-op.
Right you said move but I was thinking of swap for some reason.
I probably still wouldn't care, unless it's clear that moving to self is even required. Trying to break everything in a thousand pieces and generalizing and perfecting them individually is a lot of busywork.
I have been out of it for a while and dont miss it much, but I thought that the rule of zero, not five, was the modern goal.
Thanks for organizing for me my thoughts on why even a restricted modern subset of C++ is complicated.
Note that the community has somewhat soured on that particular proposal and it's probably not going to be enacted in its current form, precisely because it's so complicated. (https://rust-lang.github.io/rust-project-goals/2025h2/ergono...)
Complexity is not necessarily an automatic dealbreaker for a Rust language proposal—lots of Rust features are complicated because they solve problems that don't admit simple solutions—but the purpose of this particular feature is to make it easier to write code that uses reference-counted pointers and reduce how much stuff the programmer has to keep track of. It doesn't let you do anything you can't already do, it just makes it less clunky. So if people can't easily understand what it does, then that's bad.
As a side note, this might just be a me thing, but I distinguish between "complex" and "complicated". Certain things have an inherently complexity. Stripped to their essence, they're still going to have a lot of moving parts. It's just their nature. However, you can also add complication on top of something that makes it more complex than it needs to be to perform its function. Think "complication" in the watchmaker's sense. It might be neat to add a dial that shows the number of weeks since your last dentist appointment, but you don't really need that to have a functional wristwatch, and its presence makes the whole thing a lot more finicky and fragile than it would otherwise be.
Complexity is fine. Sometimes we're working on complex problems without simple, straightforward, correct solutions. I do try to avoid complications, though.
Yeah, I was slightly sloppy about that, but also the distinction doesn't entirely help at this level because people get into vicious fights about whether a particular bit of complexity is really "necessary". E.g., lots of people argue that async Rust is unnecessary complication, either because you can just write all the state machines by hand (which is terrible for expressiveness, but do you really need expressiveness?), or because you can just spawn an OS thread for every task (which is terrible for performance, but do you really need performance?). Whereas the pro-async perspective is, yes, it's very complex, but nothing simpler would have done everything that it needs to do.
Oh, I hear ya. That makes perfect sense, and you're so right: my idea of a complex solution might be someone else's complication, and vice versa. I didn't mean to disagree with you, and surely not to correct you, because not everyone agrees on the distinction I make between the ideas.
I meant that more in the spirit of "oh, while we're talking about this, here are my thoughts on a tangentially related idea".
> Complexity is fine. Sometimes we're working on complex problems without simple, straightforward, correct solutions.
Often the best way to proceed is to just solve a simpler problem :)
> Simple is better than complex. > > Complex is better than complicated.
https://peps.python.org/pep-0020/
I wonder if the block-level `clone(var_a, var_b)` was considered. It’s a bit verbose but very explicit and much better than the current situation.
Do you mean something like this? https://github.com/rust-lang/rfcs/issues/2407
I find that Rust is a language that feels insurmountable when you examine it from a distance but once you get your hands dirty it feels natural very quickly.
Case in point, my first read on lifetimes just left me confused. So I used Rc<> everywhere and made a perfectly functional program. Picking up lifetimes after mastering the basics made it a lot easier. Most people won't even need to care about lightweight clones.
> Case in point, my first read on lifetimes just left me confused. So I used Rc<> everywhere and made a perfectly functional program.
Curious, did you run into lifetime issues or just started wrapping everything in Rc<> after reading about lifetimes? Wrapping everything in Rc<> isn't even a bad thing, that's what you have to when you do WASM in a browser.
I still find it confusing sometimes because you're not setting lifetime, you're just giving it a name so that the compiler can reason about it.
Like saying something 'static doesn't make it static, it supposed to mean "live until program closes", but you can totally create things with 'static and free them before program exits.
It's been so long I can barely remember now but I'm pretty sure it was the lifetime propagation that got me... once one struct had it, every struct using that struct needed one, then there was more than one lifetime and combining them (and establishing the rules of how the lifetimes related to each other) bent my brain out of shape. Rc<> let me sidestep all of that.
Yeah, lifetime in structs are PITA. However, IMO using them is "the right choice" only in certain situations where performance hit from not using it is too much. I get why serde uses it, but notice who those don't get exposed to end user that much.
Yeah, once you're putting lifetimes in structs you're liable to run into trouble. In general you only want to do that for structs which are not expected to have long or complicated lifetimes (i.e. if you're expecting it might be allocated on the heap, it's likely too complicated to be workable)
Any time I need to explicitly give a lifetime in a struct I use an Rc or some other container. I’ve been using rust casually for years and every time I try to appease the compiler with lifetimes I realize it’s not worth my time. If someone has a very ELI5 resource that will make me understand when and why to do this I’d appreciate it.
IMO this is something that should just be handled by extra runtime code or a magically smarter compiler. Lifetime management feels like something that matters in a microcontroller or hyper-optimized setting, but never when I’m executing code on Apple Silicon for a random application. And yet the language makes simple GC ergonomically painful. I love the language but don’t really need all of that performance. I would gladly take a 1% hit to increment reference counts.
You're on Apple Silicon, which is the preferred platform for Swift. That does exactly what you're asking for wrt. ARC, and is memory safe.
there is currently a lot of ongoing discussions about "easier light weight clones" not just in the context of the RFC but in general.
And from all the discussions I have seen this RFC is one of the less promising ones as it mixes up the concept of "implicitly doing an operation before moving something into a closure scope" and "light weight clones" in a confusing ambiguous way.
So I don't expect it to be accepted/implemented this way, but I expect something similar to happen.
Like for "light weight clones" use is a pretty bad name and new syntax isn't needed, if we start shortening a `.clone()` to `.use` because it saves 4 letters then we are doing something wrong. Similar if argue for it for niche optimization reasons instead of improving generic optimizations to have the same outcome we are doing something wrong IMHO.
And for the the scoping/closure aspect (i.e. a rust equivalent of C++ closures `[]` parts (e.g. [&x](){...}) then it also seems a bad solution. First it's a operation which relates to the closure scope not the call inside of it, so attaching it to the call inside of it isn't a grate idea. Especially given that you might have multiple places you pass a clone of x in and this leading to a lot of ambiguity not highlighted with any examples in the RFC. Secondly for this concept it isn't limited to "cheap clones" sometimes you have the same pattern for not-so-cheap clones (through it mainly matters for cheap clones). And lastly if we really add ways to define captures, why not allow defining captures.
Now sure if you have a good solution for more compact way to handle "not-copy but still cheap" sharing (cloning of handles/smart pointers) of values there it maybe could make sense to also allow it to happen implicitly outside of closure capture scope instead of the invocation scope. But I would argue it's an extension of an not yet existing handle/smart pointer ergonomic improvement and should be done after that improvement.
(yes, I'm aware they use `use` because it's already a keyword, but making a non-zero cost copy of a handle/smart pointer isn't exactly "use it" but more like "share it" :/)
As someone who’s dipped their toes in it, I’d say Rust is complicated in theory but not as complicated in practice. I said on a different topic that LLMs help a bit with suggestions and you can start with suboptimal code, improving it as you get more confident.
Obviously I’m not building anything production ready in it but it’s been useful for a few apps that I’d previously been using Python for.
> Looking at the rust rfc for the lightweight clones feature
while the Use trait (a better name is proposed [1]) is useful, but I don't see how the .use syntax adds any value over .clone()? If the compiler is able to perform those optimizations, it can also lint and ask the user to remove the unnecessary .clone()/change x.clone() to &x. IMO this is better than the compiler doing black magic.
[1]: https://smallcultfollowing.com/babysteps/blog/2025/10/07/the...
Aside from the complexity not being possible to misuse (which is trivial in C++), I still find it easier. Moreover, even if you didn’t know about this if you use clippy it’ll suggest construct replacements that are more idiomatic. So even if you didn’t know about lightweight clones, you’ll get suggestions to leverage them once they’re available for your codebase (and you can apply the vast majority trivially by asking clippy to do it automatically via --fix). This is distinctly not a superpower c++ has and why the complexity of c++ keeps piling up with either no one using the new features or using them in unnecessarily complicated ways.
> This is distinctly not a superpower c++ has and why the complexity
I guess you don't have that much experience with actual C++ development? Because there's a plethora of static analysis tools and any serious IDE come with refactoring tools on top of that, both assistive and automated, that will suggest fixes as you type. Rust didn't invent anything with clippy, however good the tool might be...
I worked for Coverity and people paid us ludicrous amounts of money to get the kinds of suggestions that rustc and clippy give everyone for free. I'm a huge fan.
It is much more complex than C, sure. But it's so much simpler than C++ and you won't find yourself digging through the reference, or standard, or whatever to understand the code you are writing. It's very close to the sweet spot of "not complex enough to make you bald, not simple enough to make your code complex" while also making correctness the default.
> Yet people seem to like making useful things in it so it must have gotten something right.
I'm not commenting on Rust, seriously! But I couldn't help to notice that this sentence is a non sequitur. Something right has been developed in PHP and Visual Basic, even in Javascript and Go; still, those developers who freely choose to use those abominations, they do deserve to be pointed at and made fun of.
Go is a great language.
In my opinion, it's not just about the complexity of C++ vs Rust. It's how often the complexity (or anything else) can unpleasantly surprise you, and how serious the consequences of that surprise are.
It is definitely a complex language though I would argue probably much less so, on the whole, than C++.
Fwiw C++ has the exact same problem with moving/referencing into closures, see "Lambda capture" in https://en.cppreference.com/w/cpp/language/lambda.html.
It seems like Rust is being thorough. This is the way.
I don't think Rust is very complex. What you're doing is very complex. It's just that doing it in C/C++ lulls you into a false sense of security.
C++ is incredibly complicated. I mean there's a 278 page book just on initialization [1].
I have seen all sorts of bad multithreaded code that compilers have let someone write. It would've been much harder in Rust but Rust would've forced you to be correct. As an example, I've seen a pool of locks for message delivery where the locks are locked on one thread and unlocked on another. This would get deadlocked every now and again so every half second or so a separate process would just release all the locks.
[1]: https://leanpub.com/cppinitbook
Rust is more complex than C, yes, but as someone who has used both professionally, it is not even close to being as complex as C++. In fact it is closer in complexity to C than to C++.
Perhaps you can help guide a C expert but C++ avoider (and super-avoider of Rust, so far): If C is 1 in complexity, where does C++ and Rust fall. By 'complexity' here I mean: the ability to keep the Entire Language in your head at the same time. C, although it does have complex corners, is -- mostly -- not overly complicated. (As you can probably tell, I prefer assembly, because it is the least complicated. You can build your own abstractions up, which is the proper way to use assembly). Thank you for any insight; I'm not wedded to my views when shown a Better Way.
If C is 1 and C++ is 100 I would say Rust is like 25.
If your favorite language is assembly and C is too high-level for you then you are probably going to dislike Rust (and Java, Python, and every other modern language).
A very interesting post about this issue.
https://smallcultfollowing.com/babysteps/blog/2025/10/07/the...
> The Rust for Linux project has been good for Rust
i just decided do a good ol' 'find -name "*.rs"' in the kernel tree to get a sense for what all this is about. from what i can tell, there's just an api compatibility layer (found in /rust) and then a smattering of proof of concept drivers in tree that appear to just be simple rewrites of existing drivers (with the exception of the incomplete nvidia thing) that aren't even really in use. from what i can tell even the android binder rust rewrite is vestigial.
the whole thing seems kinda cute but like, shouldn't this experiment in programming language co-development be taking place somewhere other than the source tree for the world's most important piece of software?
redox is a pretty cool experimental piece of software that might be the os of the future, why not do it there?
The gpu driver for Apple silicon is Rust and the author stated it would have been much more difficult to implement in C. It isn't upstreamed yet.
""" Normally, when you write a brand new kernel driver as complicated as this one, trying to go from simple demo apps to a full desktop with multiple apps using the GPU concurrently ends up triggering all sorts of race conditions, memory leaks, use-after-free issues, and all kinds of badness.
But all that just… didn’t happen! I only had to fix a few logic bugs and one issue in the core of the memory management code, and then everything else just worked stably! Rust is truly magical! Its safety features mean that the design of the driver is guaranteed to be thread-safe and memory-safe as long as there are no issues in the few unsafe sections. It really guides you towards not just safe but good design. """
https://asahilinux.org/2022/11/tales-of-the-m1-gpu/
> the whole thing seems kinda cute but like, shouldn't this experiment in programming language co-development be taking place somewhere other than the source tree for the world's most important piece of software?
Torvalds seems to disagree with you.
I was just going to say, I think its hard to argue against it since it is the only way to run the GPU on the M1 / M2 Macs.
> Torvalds seems to disagree with you.
i don't really care for mindless appeals to authority. make your own arguments and defend them or don't bother.
this gpu driver looks pretty cool though. looks like there's much more to the rust compatibility layer in the asahi tree and it is pretty cool that they were able to ship so quickly. i'd be curious how kernel rust compares to user space rust with respect to bloat. (user rust is pretty bad in that regard, imo)
Mindless appeal to authority? I don't think that's how the fallacy really works. It's pretty much the authority that seems to disagree with your sentiment, that is if we can agree that Torvalds still knows what he's doing. Him not sharing your skepticism is a valid argument. The point being that instead of giving weight to our distant feelings, maybe we could just pause and be more curious as to why someone with much closer involvement would not share them. Why should we care more about the opinions of randos on hn?
> i don't really care for mindless appeals to authority. make your own arguments and defend them or don't bother.
You previously appealed to Linux being the largest worlds most important source tree and then you choose to eschew the opinion of that project's lead.
To be fair, assigning the highly competent BDFL of Linux who has listened to a bunch of highly competent maintainers some credibility isn't mindless.
Unless you have a specific falsifiable claim that is being challenged or defended, it's not at all a fallacy to assume expert opinions are implicitly correct. It's just wisdom and good sense, even if it's not useful to the debate you want to have.
Not every mention of an authority's opinion needs to be interpreted as an "appeal to authority". In this case I think they're just trying to give you perspective, not use Torvalds opinion as words from god.
> i don't really care for mindless appeals to authority.
This isn't an appeal to just any authority, but the authority that defines Linux and is its namesake.
So rather than pointing to experts who're in the best position to know, you'd prefer bad rephrasing and airchair experts? Do you 'do your own research' too?
What does rust have to do with thread safety and race conditions? Is rust going to synchronize shared memory access for me?
Speaking seriously, they surely meant data races, right? If so, what's preventing me from using C++ atomics to achieve the same thing?
> What does rust have to do with thread safety [...] ?
Rust inherently models this idea. Read about Rust's "Send" and "Sync" marker traits. e.g. https://doc.rust-lang.org/std/marker/trait.Send.html
> Is rust going to synchronize shared memory access for me?
Much better than that. (safe) Rust is going to complain that you can't write the unsynchronized nonsense you were probably going to write, shortcutting the step where in production everything gets corrupted and you spend six months trying to reproduce and debug your mistake...
aren't they just annotations? proper use of mutexes and lock ordering aren't that hard, they just require a little bit of discipline and consistency.
i can't remember the last time i faced a data race to be honest.
i guess the real question is, how well does it all hold up when you have teamwork and everything isn't strictly adherent to one specific philosophy.
> aren't they just annotations? proper use of mutexes and lock ordering aren't that hard, they just require a little bit of discipline and consistency.
Spatial memory safety is easy, just check the bounds before indexing an array. Temporal memory safety is easy, just free memory only after you've finished using it, and not too early or too late. As you say, thread safety is easy.
Except we have loads of empirical evidence--from widespread failures of software--that it's not easy in practice. Especially in large codebases, remembering the remote conditions you need to uphold to maintain memory safety and thread safety can be difficult. I've written loads of code that created issues like "oops, I forgot to account for the possibility that someone might use this notification to immediately tell me to shut down."
What these annotations provide is a way to have the compiler bop you in the head when you accidentally screw something up, in the same way the compiler bops you in the head if you fucked up a type or the name of something. And my experience is that many people do go through a phase with the borrow checker where they complain about it being incorrect, only to later discover that it was correct, and the pattern they thought was safe wasn't.
Proper use of lock ordering is reasonably difficult in a large, deeply connected codebase like a kernel.
Rust has real improvements here, like this example from the fuschia team of enforcing lock ordering at compile time [0]. This is technically possible in C++ as well (see Alon Wolf's metaprogramming), but it's truly dark magic to do so.
[0] https://lwn.net/Articles/995814/
Borrow Checker, Lifetimes, and Destructor Arguments in C++ (2024)
https://a10nw01f.github.io/post/advanced_compile_time_valida...
The lifetimes it implements is the now unused lexical lifetimes of early Rust. Modern rust uses non-lexical lifetimes which accepts a larger amount of valid programs and the work on Polonius will further allow more legal programs that lexical lifetimes and non lexical lifetimes can’t allow. Additionally, the “borrow checker” they implement is RefCell which isn’t the Rust borrow checker at all but an escape hatch to do limited single-threaded borrow checking at runtime (which the library won’t notice if you use in multiple threads but Rust won’t let you).
Given how the committee works and the direction they insist on taking, C++ will never ever become a safe language.
> Stateful Metaprogeamming
Bit of a fun fact, but as one of the linked articles states the C++ committee doesn't seem to be a fan of stateful metaprogramming so its status is somewhat unclear. From Core Working Group issue 2118:
> Defining a friend function in a template, then referencing that function later provides a means of capturing and retrieving metaprogramming state. This technique is arcane and should be made ill-formed.
> Notes from the May, 2015 meeting:
> CWG agreed that such techniques should be ill-formed, although the mechanism for prohibiting them is as yet undetermined.
[0]: https://cplusplus.github.io/CWG/issues/2118.html
> aren't they just annotations?
"Just" annotations... that are automatically added (in the vast majority of cases) and enforced by the compiler.
> proper use of mutexes and lock ordering aren't that hard, they just require a little bit of discipline and consistency.
Yes, like how avoiding type confusion/OOB/use-after-free/etc. "just require[s] a little bit of discipline and consistency"?
The point of offloading these kinds of things onto the compiler/language is precisely so that you have something watching your back if/when your discipline and consistency slips, especially when dealing with larger/more complex systems/teams. Most of us are only human, after all.
> how well does it all hold up when you have teamwork and everything isn't strictly adherent to one specific philosophy.
Again, part of the point is that Send/Sync are virtually always handled by the compiler, so teamwork and philosophy generally aren't in the picture in the first place. Consider it an extension of your "regular" strong static type system checks (e.g., can't pass object of type A to a function that expects an unrelated object of type B) to cross-thread concerns.
> aren't they just annotations? proper use of mutexes and lock ordering aren't that hard, they just require a little bit of discipline and consistency.
No, they are not. You also don't need mutex ordering as much since Mutexes in Rust are a container type. You can only get ahold of the inside value as a reference when calling the lock method.
> You also don't need mutex ordering as much since Mutexes in Rust are a container type. You can only get ahold of the inside value as a reference when calling the lock method.
Mutex as a container has no bearing on lock ordering problems (deadlock).
> proper use of mutexes and lock ordering aren't that hard, they just require a little bit of discipline and consistency.
Wow, you must be really smart! I guess you don’t need rust. For the rest of us who find concurrent programming difficult, it is useful.
> What does rust have to do with thread safety and race conditions? Is rust going to synchronize shared memory access for me?
Rust’s strict ownership model enforces more correct handling of data that is shared or sent across threads.
> Speaking seriously, they surely meant data races, right? If so, what's preventing me from using C++ atomics to achieve the same thing?
C++ is not used in the Linux kernel.
You can write safe code in C++ or C if everything is attended to carefully and no mistakes are made by you or future maintainers who modify code. The benefit of Rust is that the compiler enforces it at a language level so you don’t have to rely on everyone touching the code avoiding mistakes or the disallowed behavior.
Rust's design eliminates data races completely. It also makes it much easier to write thread safe code from the start. Race conditions are possible but generally less of a thing compared to C++ (at least that's what I think).
Nothing is preventing you from writing correct C++ code. Rust is strictly less powerful (in terms of possible programs) than C++. The problem with C++ is that the easiest way to do anything is often the wrong way to do it. You might not even realize you are sharing a variable across threads and that it needs to be atomic.
> What does rust have to do with thread safety and race conditions? Is rust going to synchronize shared memory access for me?
Well, pretty close to that, actually! Rust will statically prevent you from accessing the same data from different threads concurrently without using a lock or atomic.
> what's preventing me from using C++ atomics to achieve the same thing
You might forget?
Rust will also prevent you from sharing the unsharable between threads.
The first comment is the definition of a data race, not preventing race conditions. And data races are trivial (sure, no static prevention in C++)
> data races are trivial
Imagine this C++ code:
Now, is it okay to call `frobFoo` from multiple threads at once? Maybe, maybe not -- if it's not documented (or if you don't trust the documentation), you will have to read the entire implementation to answer that.Now imagine this Rust code:
Now, is `frobFoo` okay to call from multiple threads at once? No, and the language will automatically make it impossible to do so.If we had `&self` instead of `&mut self`, then it might be okay, you can discover whether it's okay by pure local reasoning (looking at the traits implemented by Foo, not the implementation), and if it's not then the language will again automatically prevent you from doing so (and also prevent the function from doing anything that would make it unsafe).
Rustlang doesn't aim to address race conditions. Sounds to me like overly "cautious" inefficient code you can write in any language. Think using `std::shared_ptr` for everything in C++, perchance…?
The comment probably refers to data races over memory access, which are prevented by usage of `Send` and `Sync` traits, rather than more general race conditions.
I see, but that's not the point of my comment. I don't know rustlang, perhaps I could address that if someone translated the rust-specific parlance to more generally accepted terms.
I'm not sure I understand the point of your comment at all.
Rust does, successfully, guarantee the lack of data races. It also guarantees the lack of memory-unsafety resulting from race conditions in general (which to be fair largely just means "it guarantees a lack of data races", though it does also include things like "race conditions won't result in a use after free or an out of bounds memory access").
If by address it you mean "show how C/C++ does this"... they don't and this is well known.
If by address it you mean "prove that rust doesn't do what it says it does"... as that point you're inviting someone to teach you the details of how rust works down to the nitty gritty in an HN comment. You'd be much better off finding and reading the relevant materials on the internet than someones off hand attempt at recreating them on HN.
The point of my comment is that in my experience, incompetently written, overly-cautious code tends to be more safe at the expense of maintainability and/or performance.
Sadly, I don't know rustlang, so I can't tell if the inability to describe its features in more commonly used terms is due to incompetence or the features being irrelevant to this discussion (see the title of the thread).
The thing is you aren't really asking about a "feature" of rust (as the word is used in the title of the thread), unless that feature is "the absence of data races" or "memory safety" which I think are both well defined terms† and which rust has. Rather you're asking how those features were implemented, and the answer is through a coherent design across all the different features of rust that maintains the properties.
As near as I can tell to give you the answer you're looking for I'd have to explain the majority of rust to you. How traits work, and auto traits, and unsafe trait impls, and ownership, and the borrow checker, and for it to make sense as a practical thing interior mutability, and then I could point you at the standard library concepts of Send and Sync which someone mentioned above and they would actually make sense, and then I could give some examples of how everything comes together to enable memory safe, efficient, and ergonomic, threading primitives.
But this would no longer be a discussion about a rust language feature, but a tutorial on rust in general. Because to properly understand how the primitives that allow rust to build safe abstractions work, you need to understand most of rust.
Send and Sync (mentioned up thread) while being useful search terms, are some of the last things in a reasonable rust curriculum, not the first. I could quickly explain them to someone who already knew rust, and hadn't used them (or threads) at all, because they're simple once you have the foundation of "how the rest of rust works". Skipping the foundation doesn't make sense.
† "Memory safety" was admittedly possibly popularized by rust, but is equivalent to "the absence of undefined behaviour" which should be understandable to any C programmer.
> The point of my comment is that in my experience, incompetently written, overly-cautious code tends to be more safe at the expense of maintainability and/or performance
Well, yes, but that's the whole value of Rust: you don't need to use these overly-cautious defensive constructs, (at least not to prevent data races), because the language prevents them for you automatically.
Safe Rust does. To the extend Rust interfaces that wrap kernel APIs will achieve safety for the drivers that make use of them remains to be seen. I think it will indeed do this to some degree, but I have some doubts whether the effort and overhead is worth it. IMHO all these resources would better be invested elsewhere.
Thats kinda the problem, there are concepts in rust that don't have equivalents in other common languages. In this case, rust's type system models data-race-safety: it prevents data races at compile time in a way unlike what you can do in c or c++. It will prevent getting mutable access (with a compile time error) to a value across threads unless that access is syncronized (atomics, locks, etc)
And from what I can see, rustlang mutability is also a type system construct? I.e. it assumes that all other code is Rust for the purpose of those checks?
> rustlang mutability is also a type system construct?
Yes
> I.e. it assumes that all other code is Rust for the purpose of those checks?
Not exactly, it merely assumes that you upheld the documented invariants when you wrote code to call/be-called-from other languages. For example that if I have a `extern "C" fn foo(x: &mut i32)` that
- x points to a properly aligned properly allocated i32 (not to null, not to the middle of un-unallocated page somewhere)
- The only way that memory will be accessed for the duration of the call to `foo` is via `x`. Which is to say that other parts of the system won't be writing to `x` or making assumptions about what value is stored in its memory until the function call returns (rust is, in principle, permitted to store some temporary value in `x`s memory even if the code never touches x beyond being passed it. So long as when `foo` returns the memory contains what it is supposed to). Note that this implies that a pointer to the same memory isn't also being passed to rust some other way (e.g. through a static which doesn't have a locked lock around it)
- foo will be called via the standard "C" calling convention (on x86_64 linux this for instance means that the stack pointer must be 2-byte aligned. Which is the type of constraint that is very easy to violate from assembly and next to impossible to violate from C code).
That it's up to the programmer to verify the invariants is why FFI code is considered "unsafe" in rust - programmer error can result in unsoundness. But if you, the programmer, are confident you have upheld the invariants you still get the guarantees about the broader system.
Rust is generally all about local reasoning. It doesn't actually care very much what the rest of the system is, so long as it called us following the agreed upon contract. It just has a much more explicit definition of what that contract is then C.
Also we can (in 2024 Edition) say we're vouching for an FFI function as safe to call, avoiding the need for a thin safe Rust wrapper which just passes through. We do still need the unsafe keyword to introduce the FFI function name, but by marking it safe all the actual callers don't care it wasn't written in Rust.
This is fairly narrow, often C functions for example aren't actually safe, for example they take a pointer and it must be valid, that's not inherently safe, or they have requirements about the relative values of parameters or the state of the wider system which can't be checked by the Rust, again unsafe. But there are cases where this affordance is a nice improvement.
Also "safe" and "unsafe" have very specific meanings, not the more widely used meanings. It's not inherently dangerous to call unsafe code that is well written, it's really more a statement about who is taking responsibility for the behavior, the writer or the compiler.
I like the term "checked" and "unchecked" better but not enough to actually lobby to change them, and as a term of art they're fine.
Yes. Just like C++ "const" is a type system construct that assumes all other code is C++ (or at least cooperates with the C++ code by not going around changing random bytes).
As far as I can tell, ANY guarantee provided by ANY language is "just a language construct" that fails if we assume there is other code executing which is ill-behaved.
a data race is specific kind of race condition; it's not rust parlance, but that specificity comes up a lot in rust discussions because that's part of the value
I meant the trait send sync things. I just thought it was obvious, since Rust is not the only language susceptible to data races.
> since Rust is not the only language susceptible to data races.
The point is rather that it’s not. The “trait send sync things” specify whether a value of the type is allowed to be respectively move or borrowed across thread boundaries.
I mean, reliably tracking ownership and therefore knowing that e.g. an aliased write must complete before a read is surely helpful?
It won't prevent all races, but it might help avoid mistakes in a few of em. And concurrency is such a pain; any such machine-checked guarantees are probably nice to have to those dealing with em - caveat being that I'm not such a person.
There’s no programming language called “rustlang”. It’s just rust.
Heh. This is such a C++ thing to say: “I want to do the right thing, but then my code is slow.” I know, I used to write video games in C++. So I feel your pain.
I can only tell you: open your mind. Is Rust just a fad? The latest cool new shiny, espoused only by amateurs who don’t have a real job? Or is it something radically different? Go dig into Rust. Compile it down to assembly and see what it generates. Get frustrated by the borrow checker rules until you have the epiphany. Write some unsafe code and learn what “unsafe” really means. Form your own opinion.
> from what i can tell even the android binder rust rewrite is vestigial.
This is incorrect. The android binder rust rewrite is planned to wholly replace the current C implementation.
https://www.phoronix.com/news/Rust-Binder-For-Linux-6.18
And most of the big drivers written for Apple M-series hardware are written in Rust, those are not simple rewrites or proof of concepts.
The point of the project is not meant to be an experiment in "programming language co-development", the point of the project is to use Rust in Linux kernel development. The project was started by Linux kernel developers who want to use Rust. It's been a slow start, but as you say, it's the world's most important piece of software so progress has been careful and trying to get the foundations right before going crazy with it.
The fact that Rust gets to benefit from the project too is just an added bonus.
> from what i can tell even the android binder rust rewrite is vestigial.
Vestigal how? The commit message in the version in Linus's tree (from commit eafedbc7c050c44744fbdf80bdf3315e860b7513 "rust_binder: add Rust Binder driver") makes it seem rather more complete:
> Rust binder passes all tests that validate the correctness of Binder in the Android Open Source Project. We can boot a device, and run a variety of apps and functionality without issues. We have performed this both on the Cuttlefish Android emulator device, and on a Pixel 6 Pro.
> As for feature parity, Rust binder currently implements all features that C binder supports, with the exception of some debugging facilities. The missing debugging facilities will be added before we submit the Rust implementation upstream.
----
> shouldn't this experiment in programming language co-development be taking place somewhere other than the source tree for the world's most important piece of software?
Rust for Linux did start as an out-of-tree project. The thing is that no matter how much work you do out of tree if you're serious about trying integration out you'll have to pull in experimental support at some point - which is more or less what is happening now.
Before you can have complex drivers you need the interface layer that the drivers are built on. The RfL project works on that, upstreaming more infrastructure work until there's enough to submit a complex driver. Redhat is working on nova, asahi on the apple GPU, collabora is working on one for ARM Mali. If 3 GPU drivers don't count as complex, real drivers then what does?
"Experiment" is a misnomer. Rust has been around long enough and demonstrated more than enough real advantages in writing reliable code that we know it's what we want to be doing.
But WHERE is that code in the kernel? That, I think, it the OPs point. Where is that demonstration?
It's been moving slowly at first because you need a lot of bindings done before you can do interesting work, and bindings/FFI tend to be fiddly, error prone things that you want to take your time on and get right - that's where you deal with the impedance mismatch between C and Rust and have to get all the implicit rules expressed in the type system (if you can).
It'll go faster once all the bindings are in place and people have more experience with this stuff. I've been greatly looking forward to expanding bcachefs's use of rust, right now it's just in userspace but I've got some initial bindings for bcachefs's core btree API.
Real iterators, closures, better data types, all that stuff is going to be so nice when it can replace pages and pages of macro madness.
See, for example, the binder driver merged for 6.18. It's out there, and will land when it's ready.
In discussions like this, I sometimes feel that the importance of related work like the increasing use of Rust in Android and MS land is under-appreciated. Those who think C is fine often (it seems to me) make arguments along the lines that C just needs to have a less UB-prone variant along the lines of John Regehr and colleagues' "Friendly C" proposal,[0] which unfortunately Regehr about a year and a half later concluded couldn't really be landed by a consensus approach.[1] But he does suggest a way forwards: "an influential group such as the Android team could create a friendly C dialect and use it to build the C code (or at least the security-sensitive C code) in their project", which is what I would argue is happening; it's just that rather than nailing down a better C, several important efforts are all deciding that Rust is the way forward.
The avalanche has already started. It is too late for the pebbles to vote.
0: https://blog.regehr.org/archives/1180 1: https://blog.regehr.org/archives/1287
Oof. That's a depressing read:
> This post is a long-winded way of saying that I lost faith in my ability to push the work forward.
The gem of despair:
> Another example is what should be done when a 32-bit integer is shifted by 32 places (this is undefined behavior in C and C++). Stephen Canon pointed out on twitter that there are many programs typically compiled for ARM that would fail if this produced something besides 0, and there are also many programs typically compiled for x86 that would fail when this evaluates to something other than the original value.
Some parts of the industry with a lot of money and influence decided this is the way forward. IMHO Rust has the same issue as C++: it is too complex and a memory safe C would be far more useful. It is sad that not more resources are invested into this.
I'm entirely unconvinced that a low-level† memory safe C that is meaningfully simpler than rust is even possible, let alone desirable. IMHO Basically all of rust's complexity comes from implementing the structure necessary to make it memory safe without making it too difficult to use††.
Even if it is though, we don't have it. It seems like linux should go with the solution we have in hand and can see works, not a solution that hasn't been developed or proved possible and practical.
Nor is memory safety the only thing rust brings to the table, it's also brings a more expressive type system that prevents other mistakes (just not as categorically) and lets you program faster. Supposing we got this memory safe C that somehow avoided this complexity... I don't think I'd even want to use it over the more expressive memory safe language that also brings other benefits.
† A memory-safe managed C is possible of course (see https://fil-c.org/), but it seems unsuitable for a kernel.
†† There are some other alternatives to the choices rust made, but not meaningfully less complex. Separately you could ditch the complexity of async I guess, but you can also just use rust as if async didn't exist, it's a purely value added feature. There's likely one or two other similar examples though they don't immediately come to mind.
I don't think so. First, Rust did not come from nowhere, there were memory safe C variants before it that stayed closer to C. Second, I do not even believe that memory safety is that important that this trumps other considerations, e.g. the complexity of having two languages in the kernel (even if you ignore the complexity of Rust). Now, it is not my decision but Google's and other company's influence. But I still think it is a mistake and highlights more the influence of certain tech companies on open source than anything else.
> First, Rust did not come from nowhere, there were memory safe C variants before it that stayed closer to C.
Can you give an example? One that remained a low level language, and remained ergonomic enough for practical use?
> Second, I do not even believe that memory safety is that important that this trumps other considerations
In your previous comment you stated "a memory safe C would be far more useful. It is sad that not more resources are invested into this". It seems to me that after suggesting that people should stop working on what they are working on and work on memory safe C instead you ought to be prepared to defend the concept of a memory safe C. Not to simply back away from memory safety being a useful concept in the first place.
I'm not particularly interested in debating the merits of memory safety with you, I entered this discussion upon the assumption that you had conceded them.
> Can you give an example? One that remained a low level language, and remained ergonomic enough for practical use?
They can't, of course, because there was no such language. Some people for whatever reason struggle to acknowledge that (1) Rust was not just the synthesis of existing ideas (the borrow checker was novel, and aspects of its thread safety story like Send and Sync were also AFAIK not found in the literature), and (2) to the extent that it was the synthesis of existing ideas, a number of these were locked away in languages that were not even close to being ready for industry adoption. There was no other Rust alternative (that genuinely aimed to replace C++ for all use cases, not just supplement it) just on the horizon or something around the time of Rust 1.0's release. Pretty much all the oxygen in the room for developing such a language has gone to Rust for well over a decade now, and that's why it's in the Linux kernel and [insert your pet language here] is not.
BTW, this is also why people being are incentivized to figure out ways to solve complex cases like Rcu-projection through extensible mechanisms (like the generic field projection proposal) rather than ditching Rust as a language because it can't currently handle these ergonomically. The lack of alternatives to Rust is a big driving factor for people to find these abstractions. Conversely, having the weight of the Linux kernel behind these feature requests (instead of e.g. some random hobbyist) makes it far more likely for them to actually get into the language.
It's hard to view the relatively small scope of existing Rust code in the kernel at the present time as an indictment of the utility of Rust being used in the kernel when there are major kernel maintainers who have publicly stated that they have been doing everything in their power to block any amount of Rust code from getting merged into any part of the codebase.
> shouldn't this experiment in programming language co-development be taking place somewhere other than the source tree for the world's most important piece of software?
The language co-development isn't unique to Rust. There are plenty of features in GCC and Clang that exist specifically for Kernel usage too.
> redox is a pretty cool experimental piece of software that might be the os of the future, why not do it there?
Because people want to use Rust where they use C, right now? Whereas yours is a perfectly fine criticism, it ignores that people want the good stuff, everywhere, in the things they actually use every day. And since this is something the project lead wants to do, this doesn't seem to problem/live issue.
> 'find -name ".rs"'
Since this is a Git repo, I'd go with `git ls-files '.rs'`.
> shouldn’t this be taking place somewhere else
That’s an interesting idea. You should let this guy know you disagree with his technical decision -> torvalds@linux-foundation.org
> Since the talks described in this article, the work on field projection has received an update. Lossin wrote in to inform LWN that all fields of all structures are now considered structurally pinned, so projecting a Pin will now always produce a Pin<&mut Field> or similar value.
Huh, I missed that part. It's a pretty technical point, but I'm happy they made the decision, it held up a lot of discussions.
> The final design, taking inspiration from C++, would be a form of guaranteed optimization, where constructing a new value and then immediately moving it to the heap causes it to be constructed on the heap in the first place.
Note that there's some discussion about the name of that proposal, because "optimization" gives the wrong idea (that it's optional or could depend on the backend).
I'm probably misunderstanding the complexity of the problem, but wouldn't this be solvable by just defining the right calling convention?
"Any structures larger than x, or any structures marked with a marker type, are returned by the caller providing an outref to a buffer with correct size, and the callee directly writes the structure into that buffer."
Then you could just write normal code like
and it would just work? And it would reduce unnecessary copies in a lot of other situations too? Given how much work has been put into this issue, and how much less convenient the proposed solutions are, I feel like I must be missing something.The missing piece is that it would still force you to make even larger types in many cases, such as `Result<Large, Error>`.
Essentially the problem is composability. If you are building a large type from a sequence of other large types, and one or more step is fallible, the normal return ABI breaks down very quickly.
C++ uses the term elision for these kinds of semantics.
Why make this a behind-the-scene optimization instead of just introducing `new`? That would make things much more clear for everyone.
Because constructors are really weird. Usually in Rust, when a struct is constructed, it already upholds all its invariants because construction is the "last" step in the initialization function. But with a C++-like constructor, it starts with a struct where all fields are in an invalid state, and then the struct's invariants are slowly established field by field. This is kinda impossible to square with Rust's safety promise. Even in safe languages like Java, there are often bugs when one calls other function from the constructor, that now observes the instance under construction violating its usual invariants. And this is also something Rust wants to avoid.
> But with a C++-like constructor, it starts with a struct where all fields are in an invalid state, and then the struct's invariants are slowly established field by field.
AIUI, that's why MaybeUninit<T> exists. But even if you address the issue of it being unsafe to assert that a MaybeUninit has been initialized (which &out references could in principle solve) there are real problems with this; for example, MaybeUninit<T> has no niches or free-for-use padding even when T does, so you can't just "project" MaybeUninit to individual fields except in special cases. My understanding is that C++ partial initialization has the exact same issues in principle, they just don't come up as often because the standard for code correctness is a lot less rigorous.
How does "coalesced heap construction" or "coalesced heap allocation"?
These seem like the first features that Rust in Linux bring to the Rust language that are not almost exclusively useful to the kernel. In my perception the focus on bringing features for the kernel has held up development in other parts of the language and the standard library.
As I understand it systems programming is the priority application area for Rust, and there are plenty of projects working on OSs, embedded or other bare-metal cases, as well as interoperability with complex C codebases.
At first glance, these features look quite general to me and not particularly tied to the kernel, they are important utilities for doing this kind of programming in the real world.
What's the story with C interop now with these and related changes? I'm out of the loop.
C interop is excellent and has been for years. The one piece that still needs unstable is defining/exposing varargs functions (support for calling them was stabilized many years ago). You can write almost anything you can write in C in (partly unsafe) Rust, in fact there are projects like c2rust that automate this translation.
These new features are all about making things that the kernel devs need possible in safe Rust. This often requires support for some quite fancy abstractions, some of which cannot be expressed in current stable Rust.
> C interop is excellent and has been for years.
Only if you primarily work with `cargo` and want to interact with C from Rust. The other way around has far less support and `rustc` does not standardize the object generation. This is actively preventing projects like `systemd` to adopt Rust into their project as an example.
https://github.com/systemd/systemd/pull/19598
> Only if you primarily work with `cargo` and want to interact with C from Rust.
In what way(s) does Rust's C interop depend on cargo?
> The other way around has far less support and `rustc` does not standardize the object generation.
I believe in this context the understanding is that you're going to be using `extern "C"` and/or `#[repr(C)]` in your Rust code, which gives you a plain C interface. I think attempting to use "raw" Rust code from other languages is a rare phenomenon, if it's even attempted at all.
> This is actively preventing projects like `systemd` to adopt Rust into their project as an example.
Could you point out specific instances from that thread? From a quick glance I didn't see any obvious instances of someone saying that using Rust from C is problematic.
https://github.com/rust-lang/rust/issues/73632 needs to be addressed and then integrated into meson before systemd could consider adopting rust.
> In what way(s) does Rust's C interop depend on cargo?
Do rust and cargo allow for multiple interpretations of the same C header file across different objects in the same program? That's how C libraries are often implemented in practice due to preprocessor tricks, though I wish it wasn't normal to do this sort of thing.
I would expect so. Rust and Cargo don't consume C header files directly at all. They consume bindings generated by bindgen (or hand written if you prefer). So you could probably generate mulitple bindings if you needed multiple interpretations of a C header.
If the header files are consumed by C code that is then consumed by Rust then you'll have full support for what C supports because it will be compiled by a C compiler.
Rust and Cargo do not build C programs, so, the answer to that is "no", strictly speaking.
However, some people use cargo's build scripts to build c programs, which then you can link into your Rust program. Support would then depend on whatever the person wrote with the script, which in my experience usually delegates to whatever build system that project uses. So it should work fine.
I have to disagree here a little bit. Calling C functions from Rust is a very pleasant experience, but the other way around is not so nice. You usually have to manually create types that will unpack rust collections into C compatible structures (think decomposing `Vec` into ptr, len, capacity) & then ensure that memory passed between the two sides is free'd with the appropriate allocator. Even with `cbindgen` taking care of the mindless conversions for you, you still have to put a lot of thought into the API between the two languages.
I am currently working on a fairly involved C & Rust embedded systems project and getting the inter-language interface stable and memory-leak free took a good amount of effort. It probably didn't help that I don't have access to valgrind or gdb on this platform.
I feel this might come down to the scope one has in mind when thinking of the word "interop". I think one can reasonably simultaneously claim that the interop "mechanics" are excellent in that it's generally possible to create a Rust library that quacks like a C library and that basically any C library is usable by Rust code, but the interop "ergonomics" are suboptimal in that (as you say) actually writing the glue code can be a bit of an adventure.
I think that's a fair assessment. To your point `cbindgen` makes the mechanics of the whole thing painless & linking was trivial. That's worth a lot especially when compared to other languages.
Calling C code from Rust? Pretty nice.
Writing Rust code to be called from C (but within the same application)? Doable but somewhat painful.
Writing Rust code to act like a C shared library? Quite painful and some pretty important features are missing (proper symbol versioning support being the most obvious one). Theoretically doable if you're willing to compromise.
There's also some aspects of FFI-safety that are very subtle and easy to mess up:
I would say that the C interop with Rust is pretty good but it has lots of room for improvement and it feels like very few resources have been spent on it after they got the core stuff working.Source: I've been writing a Rust library intended to be used primarily via C FFI and run into a lot of issues...
It's C++ that is problematic, C has been easy for years
I see Rust's place on low level systems programming, for everything else on userspace compiled managed languages are a much better option, systems following an architecture like Self, Inferno or Android, so I don't see a big deal with these efforts focusing on low level C like capabilities.
> for everything else on userspace compiled managed languages are a much better option
As someone who's written a number of userspace applications in many languages as well as embedded firmwares running on bare metal, Rust is a rare gem that excels at both.
Only if those userspace applications are headless, Rust exceling at GUIs is a bit of a strech.
Well https://github.com/timschmidt/egui-rad-builder has come together rather well in the last week of hacking, if I say so myself. I think building a similar app with QT, for example, would have been significantly more challenging.
I'm particularly fond of how easy it was to make all the controls live in the editor, and editable with changes appearing immediately. imgui would probably provide a similar experience, but I find C++ much more of a pain to work with than Rust.
Regarding Rust GUI framework, there is also Slint https://slint.dev
(Disclaimer: I'm one of the Slint developers.)
I looked at Slint a couple years ago when I was evaluating UI toolkits. Looks slick! The only thing that turned me off was needing an additional DSL to define UIs. Trying to learn fewer languages, more deeply, these days. Is it possible to use Slint without the DSL?
Slint does require using its DSL to define UIs, but I'd argue it's not really like learning a whole new language. It's not harder than learning the API surface of any other GUI framework.
I actually wrote a blog post about this exact topic, since it's a common question: https://slint.dev/blog/domain-specific-language-vs-imperativ...
I don't think the DSL is any good. It looks weird (like a mix of CSS and classes), and it has a side effect of locking the user to a specific product.
In your article, you mention that an imperative code in Rust looks more complicated, but this can be fixed by adding an "object tree" syntax to Rust, that allows creating trees of objects and link them like this:
This syntax could be used not only for UI, but for describing configuration, database tables and many other things. I think it would be better solution than a proprietary language.Also I think it would be better if GUI could be drawn in the editor, it would allow to use lower paid developers without expensive computer science education for making UI.
I appreciate the answer, and you taking time to write up your thoughts on it. Your point about the GUI builder working with the DSL is noted. In the egui RAD builder, I am manipulating structs which describe the UI (and which can be exported / imported as JSON) prior to code generation, which amounts to something similar. I still feel I have to context switch less, the back-and-forth is of more concern to me than learning an additional language.
That said, great work! There's plenty of room in the language for more than one solution!
That’s a compelling post. You drew me in by calling SQL a DSL. You’re right, but if you asked me to list the DSLs I use regularly I never would have thought of SQL until now. My thinking was too uptight because I didn’t consider the “domain” in DSL could be something as big as querying structured data. That makes me wonder if Rust is a DSL for the domain of general computing (obviously not in the usual intended meaning of DSL).
Like the person you’re replying to, I am generally averse to DSLs where the domain is a single project because I associate them with previous bad experiences where I spent time learning only to find it totally misses the mark. There’s also the issue of software where I’m the sole maintainer and look at it rarely. If it’s the only place I use Slint, then I’m going to be worried that I need to relearn the DSL in three months when I want to tweak the UI. Although you can probably say the same about any non-trivial framework’s API whether or not it uses a DSL.
All that said, I’ll be a bit more open to DSLs after reading your post, and if I ever need to write a GUI in Rust I’ll give Slint a shot (although that seems unlikely since I don’t typically write Rust or GUIs).
How does it handle localization and assistive technologies, UI/UX tooling for designers, 3rd party component ecosystem?
egui works with AccessKit to provide accessibility support. I haven't added anything explicitly to the RAD builder for it yet. Great idea!
But that’s no longer the choice you need to make. Ubuntu themselves have said for a couple of years now that every new GUI app they make natively for Linux is going to be Flutter and dedicated a bunch of engineers to the project to make sure it’s a first class citizen.
Beyond that, Dart / Futter are truly an absolute pleasure to use for that use case.
Yeah, I do not know Dart / Flutter much, but if I had to choose, I would either pick that, or wxWidgets, or even Tcl/Tk, but not Rust.
Sounds great for the Dart people. But unrelated to correcting misinformation about Rust. I'm not opposed to whatever language folks want to use. Just out here hacking in Rust because it suits my particular needs. And finding that it's enjoyable to hack GUIs as well as firmwares in.
No I get all that, but to the parent comments point however, there’s no way in hell I would ever pretend for a moment that one is as straightforward as the other and it’s no longer a choice between Rust and C++ both of which add a lot of complexity to a scenario where it offers very little benefit.
Over the years, I've found that the complexity is there whether or not it's been papered over by a runtime. It's wonderful to forget about, until it's not. Some applications may never run into the limitations imposed by abstracting it away, but others run into them quickly.
"There ain't no such thing as a free lunch" as the saying goes.
To which the counter argument I guess would be.. there’s no sense in trying to solve for some abstract future set of problems that the vast majority of people are never going to have ahead of time on the off chance you’re one of them.
That too requires a substantial investment of time and resources.
I think in a more pragmatic sense too that you can form a very good understanding on if you’re going to have weird special requirements just from looking at what others have done with the same tools in similar situations before you.
> there’s no sense in trying to solve for some abstract future set of problems that the vast majority of people are never going to have
> That too requires a substantial investment of time and resources.
The discussion has gotten to be pretty abstract at this point. To get back to concrete examples, the egui RAD builder I've been hacking on worked on day 1, first commit. It's been a joy to put together, and no more difficult than building GUI apps with any other toolkit I've worked with. Which causes me to question your statements about additional complexity. You can dig deep and do dark magic with Rust if you want, but you can also treat it like any high level language and get things done quickly. That's part of what makes it a rare gem to me.
Some folks don't like dealing with strict types, or with the borrow checker, but I find that the errors they illuminate for me would have been similarly serious in other languages which lacked the tooling to highlight them. Which adds to my appreciation of Rust.
One week of hacking makes every library look good, but have you shipped actual product that other people widely use with Rust GUI yet? What was the experience there and - most importantly - what were the downsides?
I've been working with egui for a couple years. Written maybe a dozen applications with it doing various things from image manipulation to 3D graphics. As I've said elsewhere in the thread, I haven't run into anything I wasn't able to accomplish with it.
Immediate mode has it's detractors, but in practice I've found it remarkably flexible, the resulting code relatively clean, and egui gets out of the way when I want to do something like blit fast graphics to a memory mapped area of the screen. Responses have been positive.
> Rust exceling at GUIs is a bit of a strech.
BTW, this happens to almost all languages. Which ACTUAL good GUIs toolkits exist? And which ACTUAL languages HAVE good integration or implementation of them?
A good GUI kit AND integration is a bigger task than do a Os or a RDBMS. (And neither are many good languages for RDBMS)
Depends on your definition of good.
I think Swift (and even ObjC) is perfect for AppKit & UIKit. I think those frameworks are pretty good and I like using them. Languages have great integration, Swift literally made around them. Those toolkits have great integrations with the macOS.
I find C# a pretty nice language for GUI, I assume it has good (maybe not great) integration with at least one of MS GUI toolkits.
I find Rust good for GUI, but right now story is meh. Wrapper style frameworks always suffer from Rust not being whatever it wraps. Pure Rust framework miss a lot of features compared to wrapper frameworks.
> Rust exceling at GUIs is a bit of a strech.
I strongly agree with this. In particular the Rust GUI libraries I've looked at have text layout / rendering that is nowhere near what should be considered adequate today (imo).
For example egui:
- It doesn't do any bidi reordering.
- It also doesn't do any font fallback so even if you didn't need bidi you can't render many languages without first acquiring an appropriate font somehow.
- Complex shaping is nowhere to be seen either.
- egui's italics look really terrible and I'm not sure why since I can't believe even synthesized ones have to look that bad.
CSS has been doing this for years and it's been doing it very well. So I am kind of disappointed that we don't have equally powerful tools in the general Rust ecosystem. Even just considering text layout / rendering libraries, only `cosmic-text` has an API that is somewhat in the right direction[1], but even it fails simply because I don't see a way to insert a button (block element) in-between the text[2].
Note that I'm not just hating on egui here, egui is amazing. Afaict it is the most complete GUI library for Rust and it's great to be able to make GUIs this easily. However I can't just not point out that it is, in fact, not perfect and doesn't truly "excel" at GUIs.
Also I have no idea how inline layout looks in other established GUI libraries like GTK and Qt so maybe I'm complaining about something that is not available most places outside a browser. If anyone knows, it would be interesting to learn how well they compare here.
[1] CSS inline layout is so complex that even this is a decent achievement, since it is not reasonable to expect most CSS features out of new Rust libraries with a significant time disadvantage.
[2] This is non-trivial because bidi reordering should happen on the whole paragraph not only on both sides of the button, so the inline layout API must handle non-text blocks in text.
This is a detail oriented list of real issues, which is wonderful! ChatGPT found open issues in the egui Github for each of your problems, so they're known about and being worked on.
However, it seems like most of the other UI toolkits discussed here, even in other languages, suffer similar issues. Which points to the difficulty of the problems. Consequently, I don't have any problem praising egui and Rust alongside. They're great! And even great can always get better! :)
I wouldn't say it's bad at GUIs either. There are some nice libraries like Iced and Slint. Some even have good accessibility support like egui.
There is a full-fledged DE written in Rust that uses Iced: https://en.wikipedia.org/wiki/COSMIC_(desktop_environment)
Indeed. Last I knew all the Rust UI libraries were declarative and/or immediate mode, both of which have their place but I’m not convinced that they’re suitable in all situations. Sometimes you need a boring “old style” imperative UI framework with deep toolbox of capable widgets (think AppKit, win32, etc), but that’s notably absent in the Rust world.
https://www.gpui.rs/ is a relatively new entrant which is described as "hybrid immediate and retained mode". Maybe worth checking out for your use cases.
Immediate mode was certainly a different paradigm to wrap my head around, but so far I haven't found anything I couldn't accomplish with egui, including some fairly complex applications like https://timschmidt.github.io/alumina-interface/
How far from "old style" is Slint?
From a quick glance, about as far as the other Rust toolkits. Looks declarative with with a relatively small number of more basic widgets (a lot of custom widget code is necessary), as opposed to e.g. AppKit which is imperative and comes with a great number of rich widgets that are ready to use out of the box.
The GTK bindings are fine.
Sure, but that's true of basically every language except maybe C++, C#, Javascript/Typescript and Dart.
GUIs are incredibly hard and most languages never get high quality GUI libraries. Rust is still pretty young and a ton of people are working on the problem so that will definitely change.
> Rust is a rare gem that excels at both.
This can be true but it can still be the case that a managed language is even better at one of them.
> for everything else on userspace compiled managed languages are a much better option
Except for thread safety.
Fearless concurrency, is only fearless for a very fine grained version of it.
In memory resources shared among threads.
Turns out threads also may share resources like out-of-process files, memory mapped regions, shared memory, databases, distributed transactions,.... where the Send and Sync traits is of no help.
Also you happen to forget Haskell has a Software Transactional Memory, and Swift also has similar protocols since version 6, and effects are a thing in OCaml, Scala, Koka, while languages like Dafny, Koka and Idris also provide similar capabilities via proofs.
That's why everybody who cares about OS safety should invest into capability-based operating systems. They take the concept of Send and Sync and implement at the runtime of the entire computer.
> for everything else on userspace compiled managed languages are a much better option
That might be true if you're developing a pure application [1], but not if you're writing a library. Have fun integrating a C# library in a Java program, a Java library in a C# program, or either of those in a Python, Node, C, C++, or Rust program. There's something to be said for not requiring a runtime.
[1] Unless you care about easy parallelization with statically guaranteed thread-safety. But hey, who needs this in the age of multicore CPUs?
OS IPC exists for a reason, aren't microservices all the rage nowadays, and Plan 9 rules?
> OS IPC exists for a reason
Message-passing IPC is much, much slower and less efficient than shared-memory communication, and inter-process IPC (both message-passing and shared-memory) is much less convenient than intra-process multi-threading. Rust is the only mainstream language, managed or otherwise, which enables safe and efficient multi-threading.
Not at all, because as I explain on another sibiling answer, it is only safe if we cut down the use cases to a very specific one that helps to sell that narrative.
Eh, modern OS-es indeed have loads of problems still, of which non-transactional access to the filesystem is in my top three, but to put those problems on Rust is a bit uncharitable. It's not Rust's fault that zero kernel devs have the courage to finally start enforcing patterns that have proven themselves useful and safe for decades, in other areas outside of IT as well (i.e. in financial accounting).
Rust plucks the fruit it can reach and it mostly stays in its lane and it's trying to expand it here and there (like Rust in Linux and embedded). I too want one ultimate language and one ultimate kernel but I don't think you and I will live to see it. Maybe our grandchildren will not as well.
This one?
> Turns out threads also may share resources like out-of-process files, memory mapped regions, shared memory, databases, distributed transactions
For multi-threaded parallelization,
aren't really relevant, and Rust directly helps with the other two aspects: In practice, your "very specific" aspect is the most important one, and the hardest to get right without Rust's Send and Sync traits and their automatic enforcement.Who gets to say what is relevant is the architect driving the project implementation.
> aren't really relevant, and Rust directly helps with the other two aspects:
Not at all, because Rust code has nothing to say about what other processes do to those resources, the only thing you can do is wrap accesses in a unsafe code block and hope for the best, that nothing was corrupted.
I think you're confusing concurrency with parallelism. I've been talking about parallelization since my first comment in this thread. There's some overlap, yes, but the aspects you listed have typically very little to do with parallelization, which is why I called them "[not] really relevant". And where they do matter to multi-threading (the shared memory part), Rust does help with correctness.
There are a lot of features being added for kernel/firmware development, they're just not on everyone's radar.
Philopp has been a particular force for change in this area.
Everytime features are mentioned it makes me go: "it's all fun and games until someone puts tokio into the kernel", better yet if rust becomes complete enough and someone makes a direct composition renderer we could have entire applications that run entirely in the kernel which could be... interesting.
It's trivial to implement an async runtime in the kernel. The kernel's workqueue is already essentially a runtime.
I was about to take offence at the use of “trivial” in this context. But then I noticed your handle, lol. You have the license to say that, thanks for your contributions!
All these features sound really awesome and would also benefit many non-kernel cases (especially generalized projections). Very happy to see Linux driving the language forward.
Are there any researches/works/agents into use LLM to auto covert some/all C code to Rust?
Ask LLM to generate rust code from chat, usb, i2c, GPU drivers - build and test it automatically? Possible?
Or start with other "smaller" projects such as sqlite, apache, nginx, etc - possible?
There's non-LLM research towards doing this which has a few success stories: https://github.com/immunant/c2rust
LLMs seem generally unsuited for the task, because they're fundamentally approximators that won't always get things right, and as a result will introduce subtle bugs. Perhaps if you paired them with some sort of formal methods... I'm not aware of anyone doing that. Tests aren't sufficient - lots of subtle bugs will not be caught by existing test suites.
Your idea of "smaller" projects is not... smaller enough. See the actual success stories for example: https://github.com/immunant/c2rust?tab=readme-ov-file#uses-o...
Darpa is interested in funding such an effort, but as far as I know that's the stage it's at, they haven't released any results.
I am scared. More than excited.
IDK, I think Rust should stick with what it is good at and not try to expand into domain that it is clearly not nicely designed for. That is, what if the best way to implement linked list in RUST is via an array of indices and NOT through RefCell or whatever it is? What if Rust will never ever have a sane way to implement linked list. What is so wrong with that? I think there should be a very clean divide between C and Rust. Rust stays in the happy Rust world and C stays on the happy C world.
I am not sure I am excited to see something like this
It's not that hard to implement a linked list in Rust in exactly the same way as in C or C++, using raw pointers, and putting a safe API around it.
In fact, IIRC exactly that is available in the standard library.
The best way to implement a linked list is with unsafe in a collection type. You write that type once, check that it’s bullet proof, and then go onto the next thing. I’ve got a sorted map using doubly linked list and I don’t think twice about it. Using an array for a linked list means the compiler can’t tell if you are being unsafe. You still have all the same problems.