hckrnws
I write production Rust code that becomes critical infra for our customers. I got tired of nil checks in Go and became a squeaky wheel in incident retros, where I finally got the chance to rewrite parts of our system in Rust during a refactor.
I admit the skill issue on my part, but I genuinely struggled to follow the concepts in this article. Working alongside peers who push Rust's bleeding edge, I dread reviewing their code and especially inheriting "legacy" implementations. It's like having a conversation with someone who expresses simple thoughts with ornate vocabulary. Reasoning about code written this way makes me experience profound fatigue and possess an overwhelming desire to return to my domicile; Or simply put, I get tired and want to go home.
Rust's safety guardrails are valuable until the language becomes so complex that reading and reasoning about _business_ logic gets harder, not easier. It reminds me of the kid in "A Christmas Story" bundled so heavily in winter gear he cant put his arms down[0]. At some point, over-engineered safety becomes its own kind of risk even though it is technically safer in some regards. Sometimes you need to just implement a dang state machine and stop throwing complexity at poorly thought-through solutions. End old-man rant.
Look, included in the concepts the article discusses are features that people have been wanting for a very long time.
Like, the ability to have multiple mut references to a struct as long as you access disjoint fields? That's amazing!!! A redo of Pin that actually composes with the rest of the language? That's pretty awesome too.
I think you're getting tied up because the the author is describing these features in a very formal way. But someone has to think about these things, especially if they are going to implement them.
Ultimately, these are features that will make Rust's safety features (which really, are Rust's reason for existing) more ergonomic and easier to use. That's the opposite of what you fear.
I feel you, but hear me out. OP is right. I've wanted pretty much everything he's talking about here for years, I just never thought of all of this in as quite a formal way as he has. We need the ability to say "this piece of code can't panic". It's super important in the domains I work in. We also need the ability to say "this piece of code can't be non-deterministic". It's also super important in the domains I work in. Having language level support for something like this where I add an extra word to my function def and the compiler guarantees the above would be groundbreaking
IMO rust started at this from the wrong direction. Comparing to something like zig which just cannot panic unless the developer wrote the thing that does the panic, cannot allocate unless the developer wrote the allocation, etc.
Rust instead has all these implicit things that just happen, and now needs ways to specify that in particular cases, it doesn't.
The problem isn't implicit things happening.
He's talking about this problem. Can this code panic?
foo();
You can't easily answer that in Rust or Zig. In both cases you have to walk the entire call graph of the function (which could be arbitrarily large) and check for panics. It's not feasible to do by hand. The compiler could do it though."Panic-free" labels are so difficult to ascribe without being misleading because temporal memory effects can cause panics. Pusher too much onto your stack because the function happened to be preceded by a ton of other stack allocations? Crash. Heap too full and malloc failed? Crash. These things can happen from user input, so labelling a function no_panic just because it doesn't do any unchecked indexing can dangerously mislead readers into thinking code can't crash when it can.
There's plenty of independent interest in properly bounding stack usage because this would open up new use cases in deep embedded and Rust-on-the-GPU. Basically, if you statically exclude unbounded stack use, you don't even need memory protection to implement guard pages (or similar) for your call stack usage, which Rust now requires. But this probably requires work on the LLVM side, not just on Rust itself.
Failable memory allocations are already needed for Rust-on-Linux, so that also has independent interest.
What about doing something that Java does with the throws keyword? Would that make the checking easier?
I think that's exactly what's being asked for here (via the "panic effect" that the article refers to)
although, I think i'd prefer a "doesn't panic" effect just to keep backwards compatibility (allowing functions to panic by default)
Or effect aliases. But given that it's strictly a syntactic transformation it seems like make the wrong default today, fix it in the next edition. (Editions come with tools to update syntax changes)
Huh? It seems to me that in these respects the two languages are almost identical. If I tell the program to panic, it panics, and if I divide an integer by zero it... panics and either those are both "the developer wrote the thing" or neither is.
In Zig, dividing by 0 does not panic unless you decide that it should or go out of your way to use unsafe primitives [1]. Same for trying to allocate more memory than is available. The general difference is as follows (IMO):
Rust tries to prevent developers from doing bad things, then has to include ways to avoid these checks for cases where it cannot prove that bad things are actually OK. Zig (and many others such as Odin, Jai, etc.) allow anything by default, but surface the fact that issues can occur in its API design. In practice the result is the same, but Rust needs to be much more complex both to do the proving and to allow the developers to ignore its rules.
[1]: https://ziglang.org/documentation/0.15.2/std/#std.math.divEx...
Could you clarify what's going on in the Zig docs[0], then? My reading of them is that Zig definitely allows you to try to divide by 0 in a way the compiler doesn't catch, and this results in a panic at runtime.
I'd be interested if this weren't true, since the only feasible compiler solutions to preventing division-by-0 errors are either: defining the behaviour, which always ends up surprising people later on, or; incredibly cumbersome or underperformant type systems/analyses which ensure that denominators are never 0.
It doesn't look like Zig does either of these.
[0]: https://ziglang.org/documentation/master/#Division-by-Zero
> the only feasible compiler solutions to preventing division-by-0 errors are either: defining the behaviour, which always ends up surprising people later on, or; incredibly cumbersome or underperformant type systems/analyses which ensure that denominators are never 0.
I don't think it's very cumbersome if the compiler checks if the divisor could be zero. Some programming languages (Kotlin, Swift, Rust, Typescript...) already do something similar for possible null pointer access: they require that you add a check "if s == null" before the access. The same can be done for division (and remainder / modulo). In my own programming language, this is what I do: you can not have a division by zero at runtime, because the compiler does not allow it [1]. In my experience, integer division by a variable is not all that common in reality. (And floating point division does not panic, and integer division by a non-zero constant doesn't panic either). If needed, one could use a static function that returns 0 or panics or whatever is best.
[1] https://github.com/thomasmueller/bau-lang/blob/main/README.m...
More specifically, Zig will return an error type from the division and if this isn't handled THEN it will panic, kind of like an exception except it can be handled with proper pattern matching.
I can't find anything related to division returning an error type. Looking at std.math.divExact, rem, mod, add, sub, etc. it looks to me like you're expected to use these if you don't want to panic.
Actually you're right, I was going by the source code which was in the link of the comment you replied to, but I missed that that was specifically for divExact and not just primitive division.
(Rust has a similar fn: https://doc.rust-lang.org/std/primitive.isize.html#method.ch... )
> Comparing to something like zig which just cannot panic unless the developer wrote the thing that does the panic
The zig compiler can’t possibly guarantee this without knowing which parts of the code were written by you and which by other people (which is impossible).
So really it’s not “the developer” wrote the thing that does the panic, it’s “some developer” wrote it. And how is that different from rust?
I have no argument against using the right tool for a job. Decorating a function with a keyword to have more compile-time guarantees does sound great, but I bet it comes with strings attached that affect how it can be used which will lead to strange business logic. Anecdotally, I have not (perhaps yet) run into a situation where I needed more language features, I felt rust had enough primatives that I could adapt the current feature set to my needs. Yes, at times I had to scrap what I was working on to rewrite it another way so I can have compile-time guarantees. Yes, here language features offer speed in implementing.
Could you share a situation where the behavior is necessary? I am curious if I could work around it with the current feature set.
Perhaps I take issue with peers that throw bleeding edge features in situations that don't warrant them. Last old-man anecdote: as a hobbyist woodworker it pains me to see people buying expensive tools to accomplish something. They almost lack creativity to use their current tools they have. "If I had xyz tool I would build something so magnificent" they say. This amounts to having many, low-quality single-purpose built tools where a single high-quality table saw could fit the need. FYI, a table-saw could suit 90% of your cutting/shaping needs with a right jig. I don't want this to happen in rust.
> Could you share a situation where the behavior is necessary?
The effects mentioned in the article are not too uncommon in embedded systems, particularly if they are subject to more stringent standards (e.g., hard realtime, safety-critical, etc.). In such situations predictability is paramount, and that tends to correspond to proving the absence of the effects in the OP.
Ah, the embedded application. Very valid point. I'm guilty of forgetting about that discipline.
I do wonder if it is possible to bin certain features to certain, uh, distributions(?), of rust? I'm having trouble articulating what I mean but in essence so users do not get tempted to use all these bells and whistles when they are aimed at a certain domain or application? Or are such language features beneficial for all applications?
For example, sim cards are mini computers that actually implement the JVM and you can write java and run it on sim cards (!). But there is a subset of java that is allowed and not all features are available. In this case it is due to compute/resource restrictions, but something to a similar tune for rust, is that possible?
I guess the no_std/alloc/std split is sort of like what you're talking about? It's not an exact match though; I think that split is more borne out of the lack of built-in support some targets have for particular features rather than trying to cordon off subsets of the language to try to prevent users from burning themselves.
On that note, I guess one could hypothetically limit certain effects to certain Rust subsets (for example, an "allocates" effect may require alloc, a "filesystem" effect may require std, etc.), but I'd imagine the general mechanism would need to be usable everywhere considering how foundational some effects can be.
> Or are such language features beneficial for all applications?
To (ab)use a Pixar quote, I suppose one can think of it as "not all applications may need these features, but these features should be usable anywhere".
> Could you share a situation where the behavior is necessary? I am curious if I could work around it with the current feature set.
But this kinda isn't about "behavior" of your code; it's about how the compiler (and humans) can confidently reason about your code?
Sorry I don't understand. The result we all want is at compile-time ensuring some behavior cannot happen during runtime. OP argues we need for features built into the language, I am trying to understand what behavior we cannot achieve with the current primatives. So far the only compelling argument is embedded applications have different requirements (that I personally cannot speak to) that separate their use case from, say, deploying to a server for your SaaS company. No doubt there are more, I am trying to discover them.
I am biased to think more features negatively impact how humans can reason about code, leading to more business logic errors. I want to understand, can we make the compiler understand our code differently without additional features, by weidling mastery of the existing primatives? I very well may be wrong in my bias. But human enginuity and creativity is not to be understated. But neither should lazyness. Users will default to "out of box" solutions over building with language primatives. Adding more and more features will dilute our mastery of the fundamentals.
For example, in substrate-based blockchains if you panic in runtime code, the chain is effectively bricked, so they use hundreds of clippy lints to try to prevent this from happening, but you can only do so much statically without language level support. There are crates like no_panic however they basically can't be used anywhere there is dynamic memory allocation because that can panic.
Same thing happens in real time trading systems, distributed systems, databases, etc., you have to design some super critical hot path that can never fail, and you want a static guarantee that that is the fact.
Perhaps there are similarities to Scala, from my anecdotal observation. Coming from Java and doing the Scala coursera course years ago, it feels like arriving in a candy shop. All the wonderful language features are there, true power yours to wield. And then you bump into the code lines crafted by the experts, and they are line for line so 'smart' they take a real long time to figure out how the heck it all fits together.
People say "Rust is more complex to onboard to, but it is worth it", but a lot of the onboarding hurdle is the extra complexity added in by experts being smart. And it may be a reason why a language doesn't get the adoption that the creators hoped for (Scala?). Rust does not have issues with popularity, and the high onboarding barrier, may have positive impact eventually where "Just rewrite it in Rust" is no more, and people only choose Rust where it is most appropriate. Use the right language for the tool.
The complexity of Rust made me check out Gleam [0], a language designed for simplicity, ease of use, and developer experience. A wholly different design philosophy. But not less powerful, as a BEAM language that compiles to Erlang, but also compiles to Javascript if you want to do regular web stuff.
I agree. IMO, Scala can be written in Li Haoyi's way and it's a pleasure to work with. However, the FP and Effect Scala people are too loud and too smart that if I write Scala in Li Haoyi's way, I feel like I'm too stupid. I like Rust because of no GC, no VM and memory safe. If Rust has features that a Joe java programmer like me can't understand, I guess it'll be like Scala.
At least from what I’ve seen around me professionally, the issue with most Scala projects was that developers started new projects in Scala while also still learning Scala through a Coursera course, without having a FP background and therefore lacking intuition/experience on which technique to apply when and where. The result was that you could see “more advanced” Scala (as per the course progression) being used in newer code of the projects. Then older code was never refactored resulting in a hodgepodge of different techniques.
This can happen in any language and is more indicative of not having a strong lead safeguarding the consistency of the codebase. Now Scala has had the added handicap of being able to express the same thing in multiple ways, all made possible in later iterations of Scala, and finally homogenised in Scala 3.
> And then you bump into the code lines crafted by the experts, and they are line for line so 'smart' they take a real long time to figure out how the heck it all fits together.
Thing is, the alternative to "smart" code that packs a lot into a single line is code where that line turns into multiple pages of code, which is in fact worse for understanding. At least with PL features, you only have to put in the work once and you can grok how they're meant to be used anywhere.
I honestly just don't believe that Rust is more complex to onboard to compared to languages like Python. It just does not match my experience at all. I've been a professional rust developer for about three years. Every time I look at python code, it's doing something insane where the function argument definition basically looks like line noise with args and kwargs, with no types, so it's impossible to guess what the parameeters will be for any given function. Every python developer I know makes heavy use of the repl just to figure out what methods they can call on some return value of some underdocumented method of a library they're using. The first time I read pandas code, I saw something along the lines of df[df["age"] < 3] and thought I was having a stroke. Yet python has a reputation for being easy to learn and use. We have a python developer on our team and it probably took me about a day to onboard him to rust and get him able to make changes to our (fairly complicated) Rust codebase.
Don't get me wrong, rust has plenty of "weird" features too, for example higher rank trait bounds have a ridiculous syntax and are going to be hard for most people to understand. But, almost no one will ever have to use a higher rank trait bound. I encounter such things much more rarely in rust than in almost any other mainstream language.
The language itself is not more complex to onboard. For Scala also not. It feels great to have all these language features to ones proposal. The added complexity is in the way how expert code is written. The experts are empowered and productive, but heightens the barrier of entry for newcomers by their practices. Note that they also might expertly write more accessible code to avoid the issue, and then I agree with (though I can't compare to Python, never used it).
Hm, you claim that Rust and Scala are not more complex to onboard than Python... but then you say you never used Python? If that's the case, how do you know? Having used both, I do think Rust is harder to onboard, just because there is more syntax that you need to learn. And Rust is a lot more verbose. And that's before you are exposed to the borrow checker.
I am not mentioning Python at all. I contrasted Rust with Scala.
> I honestly just don't believe that Rust is more complex to onboard to compared to languages like Python.
Most people conflate "complexity" and "difficulty". Rust is a less complex language than Python (yes, it's true), but it's also much more difficult, because it requires you to do all the hard work up-front, while giving you enormously more runtime guarantees.
Doing the hard work up front is easier than doing it while debugging a non-trivial system. And there are boilerplate patterns in Rust that allow you to skip the hard work while doing throwaway exploratory programming, just like in "easier" languages. Except that then you can refactor the boilerplate away and end up with a proper high-quality system.
You can see:
* no-panic: https://docs.rs/no-panic/latest/no_panic/
* Safe Rust has no undefined behavior: https://news.ycombinator.com/item?id=39564755
would be great to have no alloc too as op requested.
> I genuinely struggled to follow the concepts in this article
I read the HN comments before I read the OP, which made me worry that the post was going to be some hifalutin type wonkiness. But instead the post is basically completely milquetoast, ordinary and accessible. I'm no type theorist--I cannot tell you what a monad is--and I confess that I don't understand how anyone could be intimidated by this article.
I think you're underestimating your competency
Things like effects aren't obvious to people, at least based on my experience of trying to teach it to people
I don't agree, the article isn't going deep into effects.
The intro where they describe effects as essentially being "function colors" (referring to another article fairly often linked in hackernews) plus give lots of concrete examples (async, const, try) seems like more than enough to be obvious to the readers.
I've been on both sides of the fence here - I've bounced between two camps:
1. Go with a better type system. A compiled language, that has sum types, no-nil, and generics.
2. A widely used, production, systems language that implements PL-theory up until the year ~2000. (Effects, as described in this article, was a research topic in 1999).
I started with (1), but as I started to get more and more exposed to (2), you start looking back on times when you fought with the type system and how some of these PL-nerds have a point. I think my first foray into Higher-Kinded Types was trying to rewrite a dynamic python dispatch system into Rust while keeping types at compile time.
The problem is, many of these PL-nerd concepts are rare and kind of hard to grok at first, and I can easily see them scaring people off from the language. However I think once you understand how they work, and the PL-nerds dumb down the language, I think most people will come around to them. Concepts like "sum types" and "monads", are IMO easy to understand concepts with dumb names, and even more complex standard definitions.
> 1. Go with a better type system. A compiled language, that has sum types, no-nil, and generics.
I was looking for something like that and eventually found Crystal (https://crystal-lang.org) as a closest match: LLVM compiled, strong static typing with explicit nulls and very good type inference, stackfull coroutines, channels etc.
Go does not use LLVM at all and never has. It uses its own compiler and its own (quirky) assembler. Of course, this does not matter to most people, but LLVM compilation can be both a blessing (good interop with C/FFI) and a curse (changes to LLVM internals causing issues for languages other than C/C++).
Comment was deleted :(
Crystal is a typed Ruby. The closer to Go language would probably be Odin.
Odin is more of an alternative C than Go. V is inspired by Go has like nearly the same syntax as Go and alot of Goodies. Like Error Types, Sum Types and more.
Crystal’s syntax is similar to Ruby’s, but AFAIK the similarity more-or-less ends there.
Yeah, (non sarcastically) give those things dumb names like borrow checker and you'll get people swooning over it.
A state machine is a perfect example of a case where you would benefit from linear types.
Some things just need precise terminology so humans can communicate about them to humans without ambiguity. It doesn't mean they're inherently complex: the article provides simple definitions. It's the same for most engineering, science and language. One of the most valuable skills I've learned in my career is to differentiate between expressions, statements, items, etc. - how often have you heard that the hardest problem in software development is coordinating with other developers? If you learn proper terminology, you can say exactly what you mean. Simple language doesn't mean more clear.
I wasn't born knowing Rust, I had to learn it. So I'm always surprised by complaints about Rust being too complex directed at the many unremarkable people who have undergone this process without issues. What does it say, really? That you're not as good as them at learning things? In what other context do software people on the internet so freely share self-doubt?
I also wonder about their career plans for the future. LLMs are already capable of understanding these concepts. The tide is rising.
At the risk of sounding like a language zealot, have you ever looked at Ada? It was explicitly designed to be very readable, and for use in safety-critical systems. Ada isn't perfect in all the ways Rust isn't, and it might not be the right choice for your system, but if you're writing systems software it's worth a look. If you're writing a web backend on the other hand, it's not worth a look at all.
Unless you are going back to CGI, web backends are systems.
As I was getting into computers as a teenager, a teacher warned me that the area is such that I would have to learn new things constantly, without end. I said, fine, what's not to like?
But I now realize that as people grow, their desire to learn new things sometimes fades, and an illusion of "already knowing enough" may set in.
Don't trust that illusion. We still have to learn new things every day. (And new fancy words for simple concepts is the easy part.)
I don't think this is an old man rant, I think you made a reasonable argument. Rust is certainly at risk of becoming just as complex as c++.
I would love to introduce more rust at work, but I dread that someone is going to ask about for<'a>, use<'a>, differences between impl X vs Box<dyn X>, or Pin/Unpin, and I don't have proper answers either.
> but I dread that someone is going to ask about for<'a>, use<'a>, differences between impl X vs Box<dyn X>, or Pin/Unpin, and I don't have proper answers either.
its an issue in "someone", all those answers can be received in under 1min from AI.
Can we get a version of Rust that swaps lifetimes and ownership for a GC and a JS-style event loop? I love the DX of the language, but I don't always need to squeeze out every microsecond of performance at the cost of fighting the borrow checker.
ReasonML if you want a slightly more Rustic syntax.
> I love the DX of the language, but I don't always need to squeeze out every microsecond of performance at the cost of fighting the borrow checker.
you can use smart pointers everywhere and stop be bothered by borrow checker.
I mean, you're asking for a fundamentally different directing for the language with different tradeoffs? Why are you commenting on an article about Rust? Not everyone wants the same tradeoffs that you do.
Then why are you using rust for these tasks?
I'm not.
> Reasoning about code written this way makes me experience profound fatigue and possess an overwhelming desire to return to my domicile;
I didn't understand that you were making fun of verbosity until the word 'domicile'. I must be one of those insufferable people who expresses simple thoughts with ornate vocabulary...
The article was comprehensible to me, and the additional function colorings sound like exciting constraints I can impose to prevent my future self from making mistakes rather than heavy winter gear. I guess I'm closer to the target audience?
I experienced the same kind of fatigue when I read "there are three directions of development which I find particularly interesting: [three things I never heard about nor particularly want to get familiar with]" in the article.
And later, when I read "Because though we’re not doing too bad, we’re no Ada/SPARK yet" I couldn't help thinking that there must be a reason why those languages never became mainstream, and if Rust gets more of these exciting esoteric features, it's probably headed the same way...
Rust is complex enough as-is. There is definitely a learning curve.
But these top-comments sometimes paint with a broad brush. As in this case.
> I admit the skill issue on my part, but I genuinely struggled to follow the concepts in this article. Working alongside peers who push Rust's bleeding edge, I dread reviewing their code and especially inheriting "legacy" implementations. It's like having a conversation with someone who expresses simple thoughts with ornate vocabulary. Reasoning about code written this way makes me experience profound fatigue and possess an overwhelming desire to return to my domicile; Or simply put, I get tired and want to go home.
Two paragraphs in and nothing concrete yet. We can contrast with the article. Let’s just consider the Effects section.
It describes four examples: functions that 1) don’t unwind, 2) guaranteed termination, 3) are deterministic 4) do not “call host APIs”, which is “IO” somehow? (this last one seems a bit off)
The first point is about not panicking (keyword panic given). Point two is about not looping forever, for example. Point three can be contrasted with non-determinism. Is that jargony? A fancy-pants term for something simpler? The fourth point seems a bit, I don’t know, could be rewritten.
All of these at least attempt to describe concrete things that you get out of an “effects system”.
> Rust's safety guardrails are valuable until the language becomes so complex that reading and reasoning about _business_ logic gets harder, not easier. It reminds me of the kid in "A Christmas Story" bundled so heavily in winter gear he cant put his arms down[0]. At some point, over-engineered safety becomes its own kind of risk even though it is technically safer in some regards. Sometimes you need to just implement a dang state machine and stop throwing complexity at poorly thought-through solutions. End old-man rant.
This is just a parade of the usual adjectives with an unexplained analogy thrown in (how will these additions cripple Rust usage?). “So complex”, “over-engineered safety”, “complexity” (again), “poorly thought-throught solutions”.
TFA is about concrete things. OP here is a bunch of adjectives. And a bunch of words about complexity and not understanding would be fine if TFA did not have any understandable, practical parts. But as we’ve gone over it does...
People see Rust. Then they see a comment reacting to nondescript complexity. The rest is history.
A good anti-complexity comment would address something concrete like Async Rust. And there are plenty of such comments to vote on.
I really think that golang makes it easy to read code, rust makes it easy to write code. If Golang had sum types it would be a much nicer language to write complex applications with
I find Go code mind numbing to read. There's just _so much of it_ that the parts of the code that should jump out at me for requiring greater attention get lost in the noise. Interfaces also make reading Go more difficult than it could be without LSP - there's no `impl Xyz for` to grep for.
It's the complete opposite for me. Rust code, especially async Rust code is just full of noise the only purpose of which is to make the borrow checker shut up
Golang makes easy-to-skim code, with all the `if err != nil` after every function call.
Rust requires actual reading, like Typescript, only more detailed.
Go makes it easy to read each line of code, not necessarily to understand what the system as a whole is doing.
Go does have sum types — but the syntax is awkward and a bit transparent, so many don't recognize it as being there, and those that do don't love using it.
Can you enlighten us to what you’re talking about instead of vagueposting like this? What’s the supposed way of simulating sum types in go?
I am not sure how would you would simulate them. With enums and structs, I suppose?
However, it also has true sum types:
type SumType interface { isSumType() }
type A string
func (A) isSumType()
type B int
func (B) isSumType()
But as you can see the syntax leaves a lot to be desired and may not be all that obvious to those who are hung up thinking in other languages.This forms a closed set of types (A, B, nil -- don't forget nil!) but the compiler doesn't understand it as such and complains that the following type-switch is not exhaustive ("missing return"):
func Foo(s SumType) bool {
switch s.(type) {
case A: return true
case B: return true
case nil: return true
}
}
Also, you, the package author, may know what constitutes SumType, but the consumers of your package don't, at least not without source. Moreover, you can spread A, B, and any other implementations of SumType across many source files, making it hard to answer the question even with source. This is even a problem for the standard library, just consider go/ast and its Decl, Expr, and Stmt interfaces, none of which document what types actually implement them.> but the compiler doesn't understand ...
Right — While it does has sum types, it doesn't have some other features found in other languages.
But, of course, if one wanted those features they would talk about those features. In this discussion, we're talking specifically about sum types, which Go most definitely does have.
> nil -- don't forget nil!
This is why alternative syntax has never been added. Nobody can figure out how to eliminate nil or make it clear that nil is always part of the set in a way that improves upon the current sum types.
interfaces in go aren’t types, so no, that’s not a sum type, it’s just an interface.
The set of objects that can fulfill that interface is not just string and int, it’s anything in the world that someone might decide to write an isSumType function for.
Interfaces in Go are structurally typed but they're still types. A variable of an interface type has two components: a pointer to its dynamic type information, including virtual method table, and a pointer to its value. When you consider that any compiled Go program has a finite set of known types concretely implementing each of its interfaces, they essentially become discriminated unions, albeit without Rust's compact inline representation (unless the dynamic type is itself a thin pointer).
> it’s anything in the world that someone might decide to write an isSumType function for.
No. Notice the lowercase tag name. It is impossible for anyone else to add an arbitrary type to the closed set.
Unless your argument is that sum types fundamentally cannot exist? Obviously given a more traditional syntax like,
type SumType tagged {
A | B
}
...one can come along and add C just the same. I guess that is true in some natural properties of the universe way. It is a poor take in context, however.> I got tired of nil checks in Go and became a squeaky wheel in incident retros, where I finally got the chance to rewrite parts of our system in Rust during a refactor.
At a new job, I am writing my first microservice in golang. Used to be a Rust/C++ (kernel) and Python/PHP/JS dev (fullstack). Rust is allowed by team is heavily invested in go already.. I don't think I'll be able to convince them to learn rust! Lol
Comment was deleted :(
I think the misunderstanding here is that the article was not intended to users but to other language designers.
As a user, using a feature such as pattern types will be natural if you know the rest of the language.
Do you have a function that accepts an enum `MyEnum` but has an `unreachable!()` for some variant that you know is impossible to have at that point?
Then you can accept a `MyEnum is MyEnum::Variant | MyEnum::OtherVariant` instead of `MyEnum` to tell which are the accepted variants, and the pattern match will not require that `unreachable!()` anymore.
The fact someone does not know this is called "refinement types" does not limit their ability to use the feature effectively.
> the article was not intended to users but to other language designers.
That might be true, but it shows the direction that Rust is talking: put in the kitchen sink, just like C++ and Scala did. And _that_ is very much important for users.
I'm not sure it shows that. Even basic features of Rust we take from granted come from concepts common users do not need to understand. Borrowing of lifetime draws from affine types, but nobody cares when writing Rust code. If in 2012 you read a similar article explaining borrow checking in academic terms you would have thought Rust would be unusably hard, which is not.
Also I do not think that adding features is always bad to the point of comparing with Scala. Most of the things the article mentions will be almost invisible to users. For example, the `!Forget` thing it mentions will just end up with users getting new errors for things that before would have caused memory leaks. What a disgrace!
Then, pattern types allow you to remove panics from code, which is super helpful in many critical contexts where Rust is used in production, even in the Linux kernel once they will bump the language version so far.
> If in 2012 you read a similar article explaining borrow checking in academic terms you would have thought Rust would be unusably hard, which is not.
Ironically, Rust was unusably hard prior to the late-2018 edition. So now your academic article has to explain both the baseline borrow checker and non-lexical lifetimes, a vast increase in complexity.
Your statement contradicts itself. It was unusable hard before non-lexical lifetimes, but they vastly increased the complexity? Then maybe what’s complex for compiler writers to implement can make the user’s life easier by lowering the complexity of their code?
Nothing can really save you from architecture astronauts, except possibly Go, but I hear there are people templating Go with preprocessors, so who knows. This is a human problem. On the other hand I hear you. The moment Rust gets proper async traits an entire world of hexagonal pain will open up for the victims of the astronauts. So it will get even worse, basically. I think if we could solve the problem of bored smart people sabotaging projects it'd be amazing.
It's hard to see features through the programming language theory jargon, but solid theoretical foundations have worked well for Rust so far.
Jargon terms like "sum types" or "affine types" may seem complicated, but when you see it's actually "enums with data fields", it makes so much sense, and prevents plenty of state-related bugs.
Proposed "effects" mean that when you're writing an iterator or a stream, and need to handle error or await somewhere in the chain, you won't suddenly have a puzzle how to replace all of the functions in the entire chain and your call stack with their async or fallible equivalents.
"linear types" means that Rust will be able to have more control over destruction and lifetime of objects beyond sync call stack, so the tokio::spawn() (the "Rust async sucks" function) won't have to be complaining endlessly about lifetimes whenever you use a local variable.
I can't vouch for the specifics of the proposed features (they have tricky to design details), but it's not simply Rust getting more complex, but rather Rust trying to solve and simplify more problems, with robust and generalizable language features, rather than ad-hoc special cases. When it works it makes the language more uniform overall and gives a lot of bang for the buck in terms of complexity vs problems solved.
What worked for rust is having enums, sane-ish error handling, having sane integer types and the borrow checker, good tooling. The rest is just not that useful compared to how much garbage it creates
You didn't mention parametric polymorphism, which is incredibly useful and important to the language. I'm guessing you intentionally excluded async, but to describe it as "not that useful" would just be wrong, there is a large class of programs that can be expressed very simply using async rust but would be very complicated to express in sync rust (assuming equivalent performance).
Yes rust async isn’t good imo. You can do basically the same thing with stackfull coroutines.
Also the ecosystem is setup so you have to use tokio and everything has to be an Arc.
No, stackful coroutines requires a runtime. Not going to work on embedded, which is where async rust shines the strongest.
If you don't care about embedded that is fine. But almost all systems in the world are embedded. "Normal" computers are the odd ones out. Every "normal" computer has several embedded systems in it (one or more of SSD controller, NIC, WiFi controller, celular modem, embedded controller, etc). And then cars, appliances, cameras, routers, toys, etc have many more.
It is a use case that matters. To have secure and reliable embedded systems is important to humanity's future. We need to turn the trend of major security vulnerabilities and buggy software in general around. Rust is part of that story.
A stackfull coroutine is brittle and doesn't compose as cleanly as stackless coroutines. As a default language primitive, the latter is almost always the robust choice. Most devs should be using stackless coroutines for async unless they can articulate a technical justification for introducing the issues that stackfull coroutines bring with them.
I've implemented several stackfull and stackless async engines from scratch. When I started out I had a naive bias toward stackfull but over time have come to appreciate that stackless is the correct model even if it seems more complicated to use.
That said, I don't know why everyone uses runtimes like tokio for async. If performance is your objective then not designing and writing your own scheduler misses the point.
> You can do basically the same thing with stackfull coroutines.
...Minus the various tradeoffs that made stackful coroutines a nonstarter for Rust's priorities. For example, Rust wanted:
- Tight control over memory use (no required heap allocation, so segmented stacks are out)
- No runtime (so no stack copying and/or pointer rewriting)
- Transparent/zero-cost interop over C FFI (i.e., no need to copy a coroutine stack to something C-compatible when calling out to FFI)
"Tight control over memory use" sounds wrong considering every single allocation in rust is done through the global allocator. And pretty much everything in rust async is put into an Arc.
I don't understand what kind of use case they were optimizing for when they designed this system. Don't think they were optimizing only for embedded or similar applications where they don't use a runtime at all.
Using stackfull coroutines, having a trait in std for runtimes and passing that trait around into async functions would be much better in my opinion instead of having the compiler transform entire functions and having more and more and more complexity layered on top of it solve the complexities that this decision created.
> "Tight control over memory use" sounds wrong considering every single allocation in rust is done through the global allocator.
In the case of Rust's async design, the answer is that that simply isn't a problem when your design was intentionally chosen to not require allocation in the first place.
> And pretty much everything in rust async is put into an Arc.
IIRC that's more a tokio thing than a Rust async thing in general. Parts of the ecosystem that use a different runtime (e.g., IIRC embassy in embedded) don't face the same requirements.
I think it would be nice if there were less reliance on specific executors in general, though.
> Don't think they were optimizing only for embedded or similar applications where they don't use a runtime at all.
I would say less that the Rust devs were optimizing for such a use case and more that they didn't want to preclude such a use case.
> having a trait in std for runtimes and passing that trait around into async functions
Yes, the lack of some way to abstract over/otherwise avoid locking oneself into specific runtimes is a known pain point that seems to be progressing at a frustratingly slow rate.
I could have sworn that that was supposed to be one of the improvements to be worked on after the initial MVP landed in the 2018 edition, but I can't seem to find a supporting blog post so I'm not sure I'm getting this confused with the myriad other sharp edges Rust's sync design has.
> > And pretty much everything in rust async is put into an Arc.
> IIRC that's more a tokio thing than a Rust async thing in general. Parts of the ecosystem that use a different runtime (e.g., IIRC embassy in embedded) don't face the same requirements.
Well, if you're implementing an async rust executor, the current async system gives you exactly 2 choices:
1) Implement the `Wake` trait, which requires `Arc` [1], or
2) Create your own `RawWaker` and `RawWakerVTable` instances, which are gobsmackingly unsafe, including `void*` pointers and DIY vtables [2]
You can do rust async by moving instead of sharing data, for example
> You can do basically the same thing with stackfull coroutines.
It's clear that you don't understand the use cases that async in Rust was designed to accommodate.
> Also the ecosystem is setup so you have to use tokio and everything has to be an Arc.
It's clear that you're not at all familiar with the Rust async ecosystem, including Embassy.
I understand what that is but I just don’t care. I am guessing the vast majority of people using rust also don’t care. Justifying the decision to create this mess by saying it is for embedded makes no sense to me.
Also don’t understand why you would use rust for embedded instead of c
Embedded systems vastly outnumber classical computers. Every classical computer has several embedded systems in them. As does appliances, cars, etc. So yes they are an incredible important use case to secure our modern infrastructure.
Generics are incredibly overused in rust like most other features. I’m pretty sure most programs don’t need generics outside of use cases like HashMap
Lack of generics generally makes a modern language DoA unless some company dumps ungodly amounts of money into it.
The rust maintainers need to learn from the mistakes of the c++ design committee and understand that not adding a feature at all is in itself a desirable feature.
For example, your section on effects:
> Functions which guarantee they do not unwind (absence of the panic effect)
* I actually don’t see how this is any more beneficial than the existing no_panic macro https://docs.rs/no-panic/latest/no_panic/
> Functions which guarantee they terminate (absence of the div effect)
> Functions which are guaranteed to be deterministic (absence of the ndet effect)
> Functions which are guaranteed to not call host APIs (absence of the io effect)
The vast majority of rust programs don’t need such validation. And for those that do, the Ferrocene project is maintaining a downstream fork of the compiler where this kind of feature would be more appropriate.
I think rust is in a perfect spot right now. Covers 99.99% of use cases and adding more syntax/functionality for 0.001% of users is only going to make the language worse. The compiler itself provides a powerful api via build.rs and proc macros which let downstream maintainers build their desired customization.
> The vast majority of rust programs don’t need such validation.
The vast majority of Rust programs would benefit from such validation existing in the language, even if they never bother to explicitly leverage it themselves. For example, a non-panicking effect would be beneficial for both compilation times (don't bother pessimistically emitting unwinding glue for the enormous number of functions that don't need it, only to attempt to optimize it away later) and runtime performance (the guaranteed absence of the aforementioned glue reduces potential branch points and code size, which leads to better inlining and vectorization).
There are ways to handle these effects without bloating up normal applications. For example, using attributes implemented as intrinsics, which wouldn't really affect anything that isn't using them.
The thing is, some of these things are very useful in specific domains, and all these domains are closely related to the ideas of safety. Nondeterminism and IO are important for purity/referential transparency, which is a fairly important effect for business logic IMO. Guaranteed termination matters for formal verification. Unwind removal matters for embedded. I don't think wishing for these things is really all that unwarranted
> I actually don’t see how this is any more beneficial than the existing no_panic macro
no_panic and similar macros are doing a very hacky workaround which isn't really a great static guarantee. The simple fact that building with panic = abort makes the macro useless is an annoyance in and of itself. dtolnay did great when figuring out some path forward but it's somewhat shaky
If a piece of code does not panic in panic=unwind, then it does not panic in panic=abort either. So having coverage of panic=unwind would be sufficient to guarantee that code cannot panic. The caveat you mention with panic=abort would only apply to code that is unable to build for panic=unwind, which is uncommon.
Oh, then I misremembered and misunderstood the note, my apologies
On that note, thank you for all your work!
The "caveats" section in its docs hints at it, but to be explicit: no_panic is a band-aid that can break when changing optimizer options or compiler/llvm version. It's not a good option for library crates, e.g.
That being said, I'm not at all happy with all the complexity and ecosystem fragmentation that async brought. I understand what you're saying. But surprise panics is a bit of a pain point for me.
> It's not a good option for library crates, e.g.
Author here. Yes, it is. It was literally made for libraries. Notably https://github.com/dtolnay/zmij and https://github.com/dtolnay/itoa use it to enforce that the libraries' public API is absent of panicking.
You just know some guy will try to make cli code no-panic/deterministic use a bunch of crap and think he achieved something.
Just not allowing complex code is so much better than this.
Just even being able to look at a piece of code and trace what it is doing is 1000x more valuable than this. I regretted almost every time I allowed Traits/generics into a codebase
> I actually don’t see how this is any more beneficial than the existing no_panic macro
I think looking at the caveats listed in the no_panic docs should give you some ideas as to how a "proper" no_panic effect could improve on the macro.
Furthermore, a "proper" effect system should make working with effects nicer in general - for instance, right now writing functions that work independently of effects is not particularly ergonomic.
> The vast majority of rust programs don’t need such validation.
I think you also need to consider the niches which Rust wants to target. Rust is intended to be usable for very low-level/foundational/etc. niches where being able to track such effects is handy, if not outright required, so adding such support would be unblocking Rust for use in places the devs want it to be usable in.
> And for those that do, the Ferrocene project is maintaining a downstream fork of the compiler where this kind of feature would be more appropriate.
Given this bit from the Ferrocene website:
> Ferrocene is downstream from Rust
> It works with existing Rust infrastructure and the only changes made in the code were to cover testing requirements of ISO 26262, IEC 61508 and IEC 62304 qualification. All fixes are reported upstream for constant improvement.
I would suspect that such changes would be out of scope for the Ferrocene fork because that fork is more intended to be a qualified/certified Rust more than Rust + completely novel extensions.
> The compiler itself provides a powerful api via build.rs and proc macros which let downstream maintainers build their desired customization.
Given the complexity of the features listed this feels tantamount to asking each individual consumer to make their own fork which doesn't seem very likely to attract much interest. IIRC async even started off like that (i.e., using a macro), but that was painful enough and async thought to be useful enough to be promoted to a language feature.
I'm curious to what extent one can implement the described features using just build.rs/proc macros in the first place without effectively writing a new compiler.
I'm pretty conflicted on this comment section. A lot of people are expressing a lot of fear of C++ bloat. I get that.
I'm not sure what the right answer for Rust is, but I'm fairly convinced that these type system ideas are the future of programming languages.
Perhaps it can be added to rust in a reasonable and consistent way that doesn't ultimately feel like a kludgy language post-hoc bolt on. Time will tell. There is a serious risk to getting it wrong and making the language simply more complicated for no gain.
But, these ideas are really not obscure academic stuff. This is where programming language design is at. This moment is like talking about sum-types in the 2010s. These days that concept is normalized and expected in modern programming languages. But, that's a fairly recent development.
I suspect that Linear types, refinement types, etc will follow a similar trajectory. Whether new ideas like this can be reasonably added to existing languages in a good way is the age old question.
Hopefully Rust makes good choices on that road.
> There is a serious risk to getting it wrong and making the language simply more complicated for no gain.
I think the Rust team/community is well-aware of this. Which is why Rust has such a well-defined RFC life-cycle.
At the other end, one of the biggest complaints about Rust is that many features seem eternally locked behind nightly feature gates.
I feel the same confliction about this organizational policy. It has been refreshing that they haven't just jammed half-baked ideas into stable like C++ has done for decades. But, yes, it's frustrating to bump into an issue and discover that a fix has been proposed and implemented but has never been moved to stable in 5-10 years. Some things feel like they're languishing in nightly forever.
I don't personally have a solution to propose to this problem. I generally appreciate their caution and long-term considering. It's refreshing coming from C++. I suppose one could argue that they've overcorrected in the other direction. Unclear.
Deeper than that, I think there's a philosophical dispute on whether languages should or shouldn't even evolve. There are people with C-stability type thinking that would argue that long-term stability is so important that we should stop making changes and etch things into stone. There is some merit to that (a lot of unhelpful churn in modern programming). But, failure to modernize is eventually death IMHO. I think C is slowly dying because of exactly this. It will take quite a while because it is critical computing infrastructure. But, few people remain that defend it's viability. The arguments that remain are of the form "we simply don't have a viable replacement yet".
Perhaps you can even take the view that this is the lifecycle of programming languages. They're not supposed to live forever. That could be a reasonable take. But then you really have to confront the problem of code migration from old languages to new languages. That is a very very hard unsolved problem (e.g. see: COBOL).
Language evolution is foundationally a hard problem. And I'm not unhappy with Rust's approach. I think no one has managed to find an ideal approach.
This may be too much advanced type theory for a useful language.
You can go all the way to formal verification. This is not enough for that. Or you can stop at the point all memory error holes have been plugged. That's more useful.
You can go way overboard with templates/macros/traits/generics. Remember C++ and Boost. I understand that Boost is now deprecated.
I should work some more on my solution to the back-reference problem in Rust. The general idea is that Rc/Weak/upgrade/downgrade provide enough expressive power for back references, but the ergonomics are awful. That could be fixed, and some of the checking moved to compile time for the single owner/multiple users case.
> You can go way overboard with templates/macros/traits/generics.
You can go overboard on any language concept imaginable, but conflating all these mechanisms makes it sound like you haven't interacted much with non-C++ languages—particularly since rust doesn't have templates or anything like templates, traits are an entirely unrelated composition mechanism, and macros are entirely unrelated to the type discussion in the article.
This isn't really "advanced type theory" so much as picking up programming language developments from the 90s. I suppose it's "advanced" in the sense that it's a proper type system and not a glorified macro ala templating, but how is that a bad thing?
Agreed. Generics are in most modern typed languages, and traits are essentially interfaces. Maybe templates means C++ templates, which are essentially generics?
In C++, concepts are essentially generics where templates are more like weird macros.
My guess is they meant metaprogramming in general (templates/generics, macros), but traits are not quite like the others.
Thanks for posting this! As a long-time Rust user (and contributor, in the good old days), the thing that has always fascinated me about Rust is the healthy balance it strikes between academic brilliance and industry pragmatism. Radical changes like the ones suggested by the OP risk damaging that balance IMO. I'd rather put up with some language quirks and see Rust achieve "boring technology" status...
But who knows, maybe the "academic brilliance" from the article is more pragmatic than I give it credit for. I sure hope for it if these changes ever go through.
The lack of use cases in that document is a concern. They're all "nice to have" features, but is the payoff there for real work? The "effects" section mentions properties useful for a proof system. But it's not part of a proof system. If it were, most of those could be found automatically by static analysis, without bothering the programmer. (I was doing that decades ago in a very early proof of correctness system.) Getting programmers to annotate items is tough. Just getting C++ programmers to use "const" enough is hard.
"View types" are interesting. But how much of that generality is really needed? We already have it for arrays, with "split_at_mut" and its friends. That's a library function which uses "unsafe" but exports a safe interface. The compiler will already let you pass two different fields of a struct as "&mut". That covers the two most pressing cases.
I would also have liked to see some motivational examples, but I think the most interesting upside of an effect system is composability.
Rust is actually really unique among imperative languages in its general composability - things just compose really well across most language features.
The big missing pieces for composability are higher-kinded types (where you could be generic over Option, Result, etc.), and effects (where you could be generic over async-fn, const-fn, hypothetical nopanic-fn, etc.)
The former becomes obvious with the amount of interface duplication between types like Option and Result. The latter becomes obvious with the number of variants of certain functions that essentially do the same thing but with a different color.
> Rust is actually really unique among imperative languages in its general composability
Can you compare it to some other imperative language? Because I really don't see anything particularly notable in Rust that would give it this property.
I’m mainly comparing to the progeny of C, where the biggest difference is the fact that almost everything is an expression in Rust.
No need for ternary operators. C# unsafe blocks can only appear as statements (so you cannot delegate from a safe to an unsafe constructor, e.g.). C++ cannot return from the middle of an expression.
A related aspect is the type system, which composes with expressions in really interesting ways, so things like constant array sizes can be inferred.
Not so much composability on async Rust.
Some uses of higher-kinded types (though not all of them) can be addressed by leveraging Generic Associated Types (GAT).
Part of the problems is that the "things just compose really well" point becomes gradually less and less applicable as you involve the lower-level features Rust must be concerned with. Abstractions start to become very leaky and it's not clear how best to patch things up without a large increase in complexity. A lot of foundational PL research is needed to address this sensibly.
"View types" addresses one of the top pain points of the borrow checker, which is disjoint borrows on a single value. The compiler can understand values where only some fields are borrowed, so e.g. you can mutably borrow the currently-unborrowed fields, but there's no way to write these partial borrows and so you cannot write functions that either take or return these borrows. This often means having to restructure code to split up one struct into several child structs just to be able to borrow those substructs independently.
How are these suggestions not pragmatic? You don't have to use them, but if you need them they are there. From a security point of view I can see many of these being incredibly useful.
No, that's not how it works. See for example async: I don't need it (and indeed hate working with it, because colored functions are incredibly unpleasant to work with). But a huge swath of libraries in the ecosystem are designed with the assumption that you will use it. If the language adds more features like the article is proposing, it will most likely balkanize the crate ecosystem even further. This stuff does affect people even if they never use the features in question.
If it slows down Rust development it's not pragmatic. And if it creates a cultural schism between full commitment and pragmatic approaches, it's also trouble. Remember Scala?
Rust is nowhere near the complexity of Scala wrt. seemingly arbitrary high-level features. There's a low-level, systems programming featureset that involves quite a bit of complexity but that's also less arbitrary when comparing across similarly low-level languages.
>If it slows down Rust development it's not pragmatic.
I truly don't understand. If you don't want rust to become complex, you don't want it to "develop" fast anyways. Unless you mean you think it will be slower to write code?
> And if it creates a cultural schism between full commitment and pragmatic approaches, it's also trouble.
Zero clue what this is supposed to mean. WTF is "full commitment" here?
> Remember Scala?
Scala, haskell, and others are high level languages in "academic terms." They have high levels of abstraction. The proposals are the opposite of high level abstractions, they instead formalize very important low level properties of code. If anything they decrease abstraction.
> This may be too much advanced type theory for a useful language.
I think a lot of things taken for granted these days were considered "too complicated" some time ago: think of how widespread pattern matching, closures, generics, or functional idioms in imperative languages are, and compare to e.g. Java 1.0.
My feeling is that the "acceptable level of complexity" for programming languages goes up over time, so probably stuff like effect types will be almost everywhere in another 10 years.
Counterpoint: if any language could thrive in that valley of despair between pragmatic and theoretical excellence you're referring to, it would be Rust. Because so much of the cost is already paid for once you have satisfied the borrow checker. At least that's what I'd imagine, I could certainly be wrong.
I'm not 100% convinced that "plugging memory error holes" was right at the compiler level.
Currently building out clr, which uses a heuristic (not formal verification) method for checking soundness of zig code, using ~"refinement types". In principle one could build a more formal version of what I'm doing.
Boost is as actual as ever.
Also the way nowadays is with constexpr, templates, and around the corner, static reflection.
> This may be too much advanced type theory for a useful language.
Maybe but:
- Move fixes Pin
- Linear types, prevent memory leaks
- potentially effects simplify so many things
Each of these functionalities unlock capabilities people have complained about Rust. Namely async, gen blocks, memory leaks.
Yes. The appalling level of ignorance in the comments here and failure to understand the article is disturbing and a little scary ... hopefully it won't get in the way of actually simplifying and unifying the language, generalizing special cases, removing limitations and roadblocks, etc. as outlined in the article.
Boost is stronger than ever.
> I understand that Boost is now deprecated.
Huh?? Boost is used basically everywhere.
Definitely not. Boost is specifically prohibited in many companies. I haven’t run into Boost in a source tree in over a decade.
There are many reasons for this. Boost has uneven quality. Many of the best bits end up in the C++ standard. New versions sometimes introduce breaking changes. Recent versions of C++ added core language features that make many Boost library features trivial and clean to implement yourself. Boost can make builds much less pleasant. Boost comes with a lot of baggage.
Boost was a solution for when template metaprogramming in C++ was an arcane unmaintainable mess. Since then, C++ has intentionally made massive strides toward supporting template metaprogramming as a core feature that is qualitatively cleaner and more maintainable. You don’t really need a library like Boost to abstract those capabilities for the sake of your sanity.
If you are using C++20 or later, there isn’t much of a justification for using Boost these days.
Not at all. Most of the good parts of boost are now part of the standard library, and the rest of boost that is of high quality have stand-alone implementations, like ASIO, pybind11 (which was heavily influenced by Boost.python), etc...
A lot of the new stuff that gets added into boost these days is basically junk that people contribute because they want some kind of resume padding but that very few people actually use. Often times people just dump their library into boost and then never bother to maintain it thereafter.
Really excited for the possibilities here.
People undoubtedly thought going for Affine types was too much, and even simple things like null safety or enums-with-values and the prevalence of Result saw debate with minimalists voicing concerns.
A world where you could write a Rust program that is memory leak free with Affine types is one I want to live in. Haskell can do it now, but its just not easy and Rust has beat out Haskell with its mix of ML-strength types and practicality.
IMO these changes maintain Rusts winning mix of academia and practicality. Heres a proof point — dependent types weren't mentioned :)
*linear types (the leak free future bit)
This description is also a good crystallization of why one would want linear types
I write Rust for embedded systems and both effects and linear types would be meaningful improvements. Effects especially: if you have multiple cores in a coherent memory domain AND you run with interrupts enabled, you have to deal with three types of mutex:
- data accessed by multiple cores and interrupt handlers must be modified under a spin lock and with interrupts disabled
- data accessed by multiple cores but not interrupt handlers only needs the spin lock
- data accessed by one core but maybe interrupt handlers only needs to pay for disabling interrupts
Depending on your core and how performance sensitive the code is, the costs of the above can vary significantly. It would be nice to encode these rules in the type system.
(Ordered types might be useful for “critical sections” — that is, areas where interrupts are disabled and the interrupt disablement guard absolutely must be dropped in order.)
Reposting my comment from Reddit,
I had some Scala 3 feelings when reading the vision, I hope Rust doesn't gets too pushy with type systems ideas.
That is how we end with other ecosystems doubling down in automatic memory management with a good enough ownership model for low level coding, e.g. Swift 6, OxCaml, Chapel, D, Linear Haskel, OCaml effects,...
Where the goal is that those features are to be used by experts, and everyone else stays on the confort zone.
> I had some Scala 3 feelings when reading the vision, I hope Rust doesn't gets too pushy with type systems ideas.
I don't know if it is true or not, but my feeling is that Scala brought a lot of new ideas. But as I read somewhere, "Scala was written by compiler people, to write compilers", and I can understand that feeling.
Kotlin came after Scala (I think?) and seems to have gotten a lot of inspiration from Scala. But somehow Kotlin managed to stay "not too complex", unlike Scala.
All that to say, Rust has been innovating in the zero-cost abstraction memory safe field. If it went the way of Scala, I wonder if another language could be "the Kotlin of Rust"? Or is that Zig already? (I have no idea about Zig)
> But somehow Kotlin managed to stay "not too complex", unlike Scala.
It's not really true anymore, Kotlin has slowly absorbed most of the same features and ideas even though they're sometimes pretty half-baked, and it's even less principled than the current Scala ecosystem. JetBrains also wants to make Kotlin target every platform under the sun.
At this point, the only notable difference are HKTs and Scala's metaprogramming abilities. Kotlin stuck to a compiler plugin exposing a standard interface (kotlinx.serialization) for compile-time codegen. Scala can do things like deriving an HTTP client from an OpenAPI specification on the fly, by the LSP backend.
> JetBrains also wants to make Kotlin target every platform under the sun
So did Scala long before. It's just that Kotlin got a lot more traction for different reasons.
Not to the same extent. Scala.JS and Kotlin.JS are somewhat comparable, other targets not so much. There was no serious attempt at making Scala target mobile devices, even during the window of opportunity with Scala on Android.
> even during the window of opportunity with Scala on Android.
I don't understand this. You can run any pure Java jar on Android, pretty sure you can do that with Scala too? It's not exactly a "different platform" in terms of programming language. Sure it needs tooling and specific libraries, but that's higher level than the programming language.
Jetbrains is doing interop with Swift (Kotlin -> ObjC -> Swift and more recently Kotlin -> C -> Swift), which Scala never did. But I don't really see how this is relevant in this conversation.
You can run Scala on Android and it's been done but it never worked well nor was given priority. Which is understandable as the commercial entities behind Scala already struggle to build the ecosystem and tooling in spaces where the language shines.
For instance the Android runtime has chronically lagged behind mainline JVM bytecode versions, iirc once Scala started to emit Java 8 bytecode, Android was stuck on Java 6.
Kotlin had other obvious advantages on Android like its thin standard library or the inlining of higher-order functions.
> I wonder if another language could be "the Kotlin of Rust"?
Some people would say that Swift is that language since it's potentially memory safe like Rust and is described as friendlier to novices. There's some room for disagreement wrt. the latter point.
Well many languages are memory safe. Java has been memory safe forever. Rust is the one that is memory-safe with zero-cost abstractions, right?
Rust is also data race safe to boot.
I doubt those languages would have the same level of traction as Rust, especially now that Rust has already gotten said traction over the past decade with even the Linux kernel using them. It's more likely that Rust will be written as today and then these extra features are added for more type safety in certain functions as like I said in another comment I doubt people are going to write type contracts for every single function (maybe LLMs will, but that's an orthogonal discussion).
Apparently you missed Swift.
Linux kernel adoption of Rust hasn't been a smooth ride, exactly because of its type system among C folks.
It is only happening because the likes of Google and Microsoft want to see it through.
Swift is not really for systems level programming like Rust and interestingly some projects like Ladybird have moved away from Swift towards Rust.
> Swift is not really for systems level programming
Apple seems to think differently.
> and interestingly some projects like Ladybird have moved away from Swift towards Rust.
Not really interesting after you spent some time tracking lifecycle of FOSS projects. I don't think it is a last "moving away" announcement we get from Ladybird.
It certainly is for Apple, regardless of your opinion.
It is all over the place in Swift documentation, WWDC sessions, and even last week on Meet the Team session, regarding on how to write safe systems programming code on Apple platforms.
Ladybird should focus on what language they actually want to deliver something.
My understanding is that Scala 3 came with many large breaking changes that made adoption difficult. I at least hadn't heard users complain that new features weren't desired.
Doubt Rust will ever get to implicit hell of Scala 2.
If for anything, Rust isn't married to C as Scala is to Java.
It certainly is in what concerns ABI support, and dependency on LLVM.
Scala had an unnatural dependency on Java, not JVM. I.e., imitating not just some patterns but copying its flawed interfaces. For example, Scala could express and bypass decades of cruft in the Java Collections API. Did it choose to do it?
To paraphrase former Scala developer and present poker player, "If Java cut its nose, would you Scala... Oh god, stop! The blood, the blood!"
Everyone who wants to talk to low-level code has to have C ABI. The equivalent Scalaism in Rust would be if Rust reimplemented the C-inspired java.util.Date. Yes. Monday should be 0, not an enum. Because C did it.
From the historical sources I could find online, it appears that Rust's borrow system was independently invented, or at least they don't mention linear logic or anything substructural. This is kind of interesting to me, especially given the reactions in this thread, and ties into the general difficulty of PL research to find acceptance among practitioners, especially when presented by researchers (which I think is regretful, I like the ideas in the article!). Perhaps we really should stick to terminology like "function colors" to make effect systems more popular (or not, because the color framing makes it sound bad to have different colors in a program, IIRC).
It's the jargon, I think. PL research is in an awkward position, where the jargon is not shared with the much wider community of people using programming languages daily. From the other side, it looks like there is a small body of theoreticians using impenetrable language for discussing topics I'm supposed to be familiar with, because they are a core part of my day job. It's much easier to accept jargon, when it's used in a clearly separate field.
Some of the terminology is just unfortunate. For example, I have an intuitive understanding of what a type means. The meaning used in PL theory is somehow wider, but I don't really understand how.
And then there is my pet peeve: side effect. Those should be effects instead, because they largely define the observable behavior of the program. Computation, on the other hand, is a side effect, to the extent it doesn't affect the observable behavior.
But then PL theory is using "effect" for something completely different. I don't know what exactly, but clearly not something I would consider an effect.
Man who uses arithmetic upset at research mathematicians for using words like R-module when they clearly do not mean a module in C++
More at 11
I don't remember where I read it, but I think Rust cited Cyclone as an influence, a variation of C with "region-based" memory management - more or less the literature name for "lifetimes". I think Rust may be the first to use it directly for stack variables, however.
Rust's discussion boards has an idea of "keyword generics" for expressing some of these concepts. The idea is that a function can be generic over const, async or some other keyworded effect. I like this description. It shows the benefits without too much theory.
Comment was deleted :(
I'm surprised by the backlash in the comment section here, all of these things seems like the obvious next step for Rust. It seem people are scared of big words?
> It seem people are scared of big words?
Classic "skill issue" hubris by the language community.
When I wrote my very first Rust code, I was trying to write to a socket. I got stuck on this task with misleading error messages for the longest time. I finally realized I had not made the socket object mutable. I’m used to Posix where you have an integer file descriptor and I don’t tend to think of socket write as a mutable operation. At least it doesn’t mutate state that my app manages. Perhaps something in the kernel gets mutated. I believe the socket interface may have been intended to support queuing which is perhaps why it needed to be mutable. I might have needed a lower level api. I just mention this because I think it’s interesting as to how it should be typed when mutation is external to the app. I didn’t follow through on using Rust and this was long ago so I’m sure some details are wrong.
`std::net::TcpStream`? Interestingly, it doesn't need to be mutable if you know the trick.
* It implements `Write` for `TcpStream` which is likely what you were using. And `Write` requires `&mut`, probably because the same trait is used for writing to application buffers (e.g. through `BufWriter` or just to a `Vec`). So this doesn't compile: [2] I think the error message is pretty good; maybe it's improved since you tried. It doesn't though suggest the trick below.
* It also implements `Write` for `&TcpStream`. So if you use the awkward phrasing `(&stream).write_all(b"asdf")`, you don't need mutable access. [3] This allows you to use it with anything that takes the trait, without requiring mutability. You might need this if say you're reading from the socket from one thread while simultaneously writing to it from another thread.
It's a vaguely similar situation with the most common async equivalent, `tokio::net::TcpStream`. The most straightforward way to write to it is with a trait that needs mutable access, but it is possible to write to a shared reference by not using a trait. They also have these `split` and `into_split` methods to get mutable access to something that implements the read traits and something that implements the write traits.
[1] https://doc.rust-lang.org/std/net/struct.TcpStream.html
[2] https://play.rust-lang.org/?version=stable&mode=debug&editio...
[2] https://play.rust-lang.org/?version=stable&mode=debug&editio...
> got stuck on this task with misleading error messages for the longest time.
Could you elaborate on that? We consider misleading error messages to be bugs and would like to know more on case we could fix them.
I also found it really unintuitive what needs to be made mutable. And still do to some degree.
When I first had learned that rust had this concept of “opt-in” mutability, I thought that it must then be an accepted pattern that we make as little as possible be mutable in an attempt to help us better reason about the state of our program. I had come to rust after learning some clojure so I was like “ahh! Immutable by default! So everything’s a value!”
But in reality it feels like rust code is not “designed” around immutability but instead around appeasing the borrow checker - which is actually pretty easy to reason about once you get the hang of the language. But it’s a ton to learn up front
I think part of it might be just that for modeling's sake it makes it clear when something can write vs only read the socket
I'm not sure why people are so deeply scared. these are all pretty neat features for people who will need them (off rip seemingly mostly in the embedded world). It's not like the inclusion of these forces you to use them — I've never had to deal with unsafe rust for shipping web stuff, and I highly doubt I'd have to deal with most of these. For modeling's sake it would be nice to have pattern types and view types, I can see them being useful
People will push this crap into production codebases and link to these articles when you say you don’t want complexity. Best way to manage is to not make it possible, like go.
You indirectly deal with this kind of thing when compiling web server code too. It compiles super slow and can have weird errors. This is because the people who build the web stack in rust used a million traits/generics/macros etc.
Even if you look at something like an io_uring library in rust, it uses a bunch of macros instead of just repeating 3 lines of code.
3 lines of code that may need to change is much more complex than a macro that signals intent.
https://docs.rs/io-uring/latest/io_uring/opcode/index.html
You can click the source code link and read the code here. Macros aren’t needed at all if every single operation isn’t a different type.
This was the exact same argument used to push new c++ features and look where the language is now.
you mean better than ever?
...a far better place than 2011.
Surprised with the push back of the comments, getting effects on Rust would be a dream.
Could even enable some stuff like passing loggers around not by parameters but by effect.
Rust needs to be careful that it doesn't invite the type-programming crowd in too much. Even if individual features are helpful, the cultural effect on the programming language in terms of the community (i.e. libraries and idioms) can really fragment and destroy a language. Scala, which I programmed in for many years, is a good example -- lots of great features but ultimately overwhelmed by typesafe abstractions that held theoretical appeal but hampered readability and fragmented the community. (Lack of backwards compatibility and poor tooling also hurt Scala no doubt about it, just as slow compile times hurt Rust.)
Rust is the wrong language for effects ironically because of its strict typing. They would probably decide that all effects have to be specified on every function or that any change in capabilities is a breaking change. Which is safest, but horrible for dev ex. Whereas Go most people would just be like “yeah, Hyrum’s law, sorry I broke your weird effect consumer, I don’t really care.”
Finally seeing more movement on effects or what started as keyword generics, there was a big blog post a few years ago but not much public facing news although of course they've been working on it as Yoshua says in the post.
I truly do wish we get closer to Ada and even Lean in terms of safety, would be great to see all these theoretical type system features become reality. I use the `anodized` crate right now for refinement type features, and who knows, maybe we get full fledged dependent types too as there aren't many production languages with them and certainly not popular languages.
gotta admit i groaned a bit at this because it would make rust more complicated, but on my 2nd read i realized:
- some things (compile time bounds checking tensor shapes) are hard / impossible to implement now; "pattern types" could be great for that
- however "no panic" is already handled by clippy, might not be much uplift for doing that at a type level.
my 2c: it's great to be excited and brainstorm, some of these ideas might be gold. conveying the benefit is key. it would be good to focus on stuff for which rust doesn't already have a workable solution. i like the pattern types, the rest would take convincing
No-one ever has the "Grand Vision" to cut something down to it's essential 25% and delete the rest.
You can't cut down a language without breaking changes. And there fairly often are internal changes to simplify the compiler and the tooling. Just in the latest release, annotation internals were finally fully unified between rustc, cargo, clippy and other tools. This was a fairly considerable effort in deduplicating code. Borrow checker is being rewritten to move to tree borrows, trait resolution is being refactored for a variety of reasons. Code is being simplified where possible, but this doesn't mean Rust doesn't have areas to improve in, especially if there is a goal of replacing Ada/SPARK
Unless there's a conscious and constant effort to keep things simple, a programming language or software project will grow to consume all available resources, developers' time, intellectual capacity, and funding.
By all means, write some Go instead—you might want to order a new `if err != nil` button to stave off the RSI. The rest of us are trying to make things better.
Chuck Moore of Forth fame.
There are some solid ideas here and would definitely apply to the IVM engine we're building. I'm curious if some of these effects could play a role in faster rust compilation times (e.g. nopanic..)?
I just want to be able to call Default::default() from within const {}
That's on the way. Already available in nightly for select types.
I do not write Rust. I have left c++ cause of the feature creep of the last years that made it even harder to read c++. Reading code is so important. Make language not unfriendly for readers and value the time people put into learning a language. I have seen many c++ programmers leaving cause they feel its an other language now
I couldn’t disagree more. Most of my company’s backend code is written in Scala, and most of our engineers dislike it because the language is difficult to understand, has way too many features, and has many ways to solve the same problem. I don’t want Rust to continue down this path, and I already worry with some of the syntactic sugar and type system additions being discussed that it already has.
A language’s type system doesn’t need to model every possible type of guarantee. It just needs to provide a type safe way to do 95% of things and force its users to conform to use the constructs it provides. Otherwise it becomes a buggy hodge podge of features that interact in poor and unpredictable ways. This is already the case in Scala; we’ve discovered almost 20 bugs in the compiler in the past year.
> A language’s type system doesn’t need to model every possible type of guarantee
Actually this is the exact point of a type system. Why would you want to write unit tests for stuff the compiler can guarantee for you at the type system level?
There is a middle ground. People seem to use Haskell and OCaml just fine and both are as expressive, so maybe it is just Scala having shoved in too many things. Based on what the article shows, it doesn't seem like they're making ten different ways to do the same thing but rather one way to (optionally) get more type safety out. I doubt everyone will be writing dependent type contracts for every single function, it's more for certain pieces of the codebase.
Ah yes Haskell, the reasonable centrist position in language design.
Compared to the other languages listed in this thread, it definitely is, and it has production systems unlike them.
Ever heard of Apple, Facebook, Jane Street, HP?
I think they were saying haskell is a production language
They were downplaying the other examples.
You got a point there.
I would love to have a use case to learn and write rust today. But i am deep in node and go services for my employer. Previously wrote java and c#. What are people writing in rust today?
I use Rust for command line applications.
I find that CLI is a great way to model problems. When I find myself doing something that has graduated beyond a comfortable amount of PowerShell, Rust is there for me.
I have a template I've been evolving so it's super easy to get started with something new; I just copy the template and slam Copilot with some rough ideas on what I want and it works out.
https://github.com/teamdman/teamy-rust-cli
Just today used it to replace a GitHub stats readme svg generator thing that someone else made that was no longer working properly.
https://github.com/TeamDman/teamy-github-readme-stats
Decomposes the problem very nicely into incrementally achievable steps
1. `fetch <username>` to get info from github into a cache location 2. `generate <username> <output.svg>` to load stats and write an svg 3. `serve` to run a webserver to accept GET requests containing the username to do the above
Means that my stuff always has `--help` and `--version` behaviours too
I'm working on a multisig sign-off solution, with the first use case being file downloads like GitHub releases authentication: https://github.com/asfaload/asfaload
I'm coming from F# and find rust a good compromise: great type safety (though I prefer the F# language) with an even better ecosystem. It can also generate decently sized statically compiled executables, useful for CLI tools, and the library code I wrote should be available to mobile apps (to be developed).
We built Feldera's engine in Rust: https://github.com/feldera/feldera
I'm having fun using it to make websites. Rust→WASM works really well. Definitely a very enjoyable way to make web apps. I've been trying to think how I can contribute to the ecosystem, seeing as I enjoy it so much. Rust gives you a control over memory that is impossible to replicate in javascript, and which allows much more performant code
> What are people writing in rust today?
Rewriting existing code for karma points and GitHub stars. Plus some minority actually trying to build something new.
Whatever I used to use Node for, like web servers, I now use Rust. It's pretty nice, with a strong OCaml-like type system which I've used before (better than TypeScript even in some cases), plus it's much faster and more memory efficient such that I can run way more services on my 5 dollar Hetzner box with Dokploy compared to Node or Java or C#.
Especially borrowing parts of objects reminds me of https://vale.dev
This sounds insane at this point. The language already has too many features. Would be cool if all these people with amazing visions could move it elsewhere.
Rust is fast tracking being as bad as c++ in terms of just garbage in it.
IMO the worst thing about c++ isn't that it is unsafe but it is extemely difficult to learn to a satisfying degree.
This is already kind of feels true for Rust and it will be surely true if people just keep shoving their amazing ideas into it.
IMO even async/tokio/error-handling aren't that well though out in rust. So much for keeping things out of the language.
Maybe Rust just wasn't what I wanted and I am salty about it but it feels a bit annoying when I see posts like this and considering where Rust is now after many years of shoving stuff into it
> The language already has too many features.
That's actually the point. Many of these additions can be phrased as unifying existing features and allowing them to be used in previously unusable ways and contexts. There's basically no real increase in user-perceived complexity. The Rust editions system is a key enabler of this, and C++ has nothing comparable.
It has clang tidy, -std=lang-version, and preprocessor that is version aware.
Rust editions don't cover all use cases that one can think of regarding language evolution, and requires full access to source code.
> requires full access to source code
What do you mean? Editions don't require full access to source code. Rust in general relies heavily on having access to source code, but that has nothing to do with how editions work
You can write a binary library that exposes a C ABI using Rust (which is indistinguishable from an ordinary C/C++ library) and then provide source for a Rust wrapper crate that provides a "safe" interface to it, much like a C header file.
Yes they do, when mixing crates from various editions and how changes interact together.
> when mixing crates from various editions and how changes interact together.
Could you elaborate more on this? It's not obvious to me right now why (for example) Crate A using the 2024 edition and Crate B using the 2015 edition would require both full access to both crates' source beyond the standard lack of a stable ABI.
Because in order to have standard library breaking changes across editions, if those types are exposed in the crate public types, or change their semantics across editions, the compiler has to be able to translate between them when generating code.
See the Rust documentation on what editions are allowed to change, and the advanced migration guide on examples regarding manual code migration.
Not so much what has happened thus far, rather the limitations imposed in what is possible to actually break across editions.
Or put another way, a hypothetical feature that you made up in your head is the thing that requires source access. Editions do not let you change the semantics of types.
To be fair, Rust tooling does tend toward build-from-source. But this is for completely different reasons than the edition system: if you had a way to build a crate and then feed the binary into builds by future compilers, it would require zero additional work to link it into a crate using a different edition.
Exactly, hence why people should stop talking about editions as if they sort out all Rust evolution problems, in your own words it doesn't allow changing type semantics
I think you're too stuck on the current implementation. Work is going into investigating how to evolve the standard library over editions. The "easiest" win would be to have a way to do edition-dependent re-exports of types.
What I am stuck is Rust folks advocating editions as the solution for everything in language evolution, when it clearly isn't.
What you're describing sounds more like a potential issue with editions if/when they allow breaking stdlib changes more than a problem with editions as they exist today, which is more what I took the original comment to be talking about.
Exactly because they don't allow it, they don't cover all scenarios regarding language evolution
OK, sure, but again what breaking changes editions do/don't currently allow is independent from what SkiFire13/I was responding to, which was the "requires full access to source code" bit.
How do you expect a compiler to be able to mix and match changes across editions between crates, if those happen to be changes in semantic behaviour?
Depends on the change. Obviously the compiler doesn't need to care about cross-edition compatibility between crates if the changes in question don't impact the public API. Otherwise, I'd expect the compiler to canonicalize the changes, and from what I understand that is precisely how edition changes are chosen/designed/implemented.
Unifying surface features increases the combinatorics of interactions between traits, lifetimes, generics, specialization, and macros and leads to surprising edge cases.
Editions buy migration safety and let the standard evolve, but they do not shrink the mental model newcomers must carry and they force tooling and libraries to support multiple modes at once, which is a different kind of maintenance tax than evolving C++ compilers and feature test macros impose.
Require RFCs to include an interaction test matrix, compile time and code size measurements, and a pass from rust-analyzer and clippy so ergonomics regressions are visible before users hit them.
> and they force [] libraries to support multiple modes at once,
I'm not entirely sure I agree? I don't think any library except for the standard library needs to "support multiple modes at once"; everything else just sets its own edition and can remain blissfully unaware of whatever edition its downstream consumer(s) are using.
> which is a different kind of maintenance tax than evolving C++ compilers and feature test macros impose.
I'm not sure I agree here either? Both Rust and C/C++ tooling and their standard libraries needs to support multiple "modes" due to codebases not all using the same "mode", so to me the maintenance burden should be (abstractly) the same for the two.
> Require RFCs to include an interaction test matrix, compile time and code size measurements, and a pass from rust-analyzer and clippy
IIRC rustc already tracks various compilation-related benchmarks at perf.rust-lang.org. rustc also has edition-related warnings [0] (see the rust-YYYY-compatibility groups), so you don't even need clippy/rust-analyzer.
In practice library authors must consider the editions used by downstream crates because public APIs cross crate boundaries. Even if a crate compiles under a single edition, exported APIs often avoid edition specific idioms that could cause friction for consumers compiled under older editions. This leads to a conservative design style where libraries effectively target the lowest common denominator of the ecosystem. The result is that authors informally maintain compatibility across editions even if the compiler technically allows them to ignore downstream edition choices.
Large Rust organizations often run mixed-edition workspaces because upgrading hundreds of crates simultaneously is impractical. Libraries in the workspace therefore interact across editions during migration periods. So while technically each crate chooses its edition, ecosystem reality introduces cross-edition friction.
Feature test macros in C and C++ primarily gate access to optional APIs or compiler capabilities. Rust editions can change language semantics rather than merely enabling features. Examples include changes to module path resolution, trait object syntax requirements such as dyn, or additions to the prelude. Semantic differences influence parsing, name resolution, and type checking in ways that exceed the scope of a conditional feature macro.
Tooling complexity is structurally different. Rust tools such as rustc, rust analyzer, rustfmt, and clippy must understand edition dependent grammar and semantics simultaneously. The tooling stack therefore contains logic branches for multiple language modes. In contrast, feature test macros generally affect conditional compilation paths inside user code but do not require parsers or analysis tools to support different core language semantics.
Rust promises permanent support for previous editions, which implies that compiler infrastructure must preserve older semantics indefinitely. Over time this creates a cumulative maintenance burden similar to maintaining compatibility with many historical language versions.
> Even if a crate compiles under a single edition, exported APIs often avoid edition specific idioms that could cause friction for consumers compiled under older editions.
Do you have some concrete examples of this outside the expected bump to the minimum required Rust version? I'm coming up blank, and this sounds like it goes against one of the primary goals of editions (i.e., seamless interop) as well.
> So while technically each crate chooses its edition, ecosystem reality introduces cross-edition friction.
And this is related to the above; I can't think of any actual sources of friction in a mixed-edition project beyond needing to support new-enough rustc versions.
> Rust tools such as rustc, rust analyzer, rustfmt, and clippy must understand edition dependent grammar and semantics simultaneously.
I'm not entirely convinced here? Editions are a crate-wide property and crates are Rust's translation units, so I don't think there should be anything more "simultaneous" going on compared to -std=c++xx/etc. flags.
> Over time this creates a cumulative maintenance burden similar to maintaining compatibility with many historical language versions.
Sure, but that's more or less what I was saying in the first place!
This comparison is useless until rust commits to a stable ABI.
Can you explain how this is relevant here?
Adding a new feature to unify existing features is perfectly isomorphic to defining a new standard to unify existing standards.
It doesn't have too many features, it arguably does not have enough. The issue is that the current features don't play nicely with each other, so much of the work has been in making sure they do, such as with async traits as an example: there is no reason why you can make a function async but not inside a trait, and this was the case until very recently.
Beyond that, what the article shows is exactly what I want, I want as much type safety as possible especially for critical systems code which is increasingly what Rust is being used for.
Re: async in traits, the feature was delayed because it relied on the "Generic Associated Types" and "Impl Trait in Traits" features. If Rust delayed the whole `async` feature for working on those pretty type-theoretic features what would you have thought?
Well in practice the async_trait crate worked just fine. If Rust delayed the whole async feature I'd have thought they'd have better been able to handle the function coloring problem via something like OCaml's algebraic effects rather than following the trend of JS and C# back then, as OCaml's came along much later after more research into the model.
This inevitably happens when the approach to language design is "try it and see". I know people here hate design-by-committee, but historically it's led to some very cohesive languages.
> design-by-committee
I don't think it is about having committee, but rather having a spec. And I mean spec, not necessarily ISO standard. There should be a description of how specific features work, what is expected behavior, what is unexpected and should be treated as bug, and what is rationale behind specific decision.
Coincidentally people here hate specs as well, and that explains some things.
I know there is some work on Rust spec, but it doesn't seem to progress much.
AIUI, that is what the MIR formalization work is about, and it seems to be moving along fine. My impression is that covers essentially all the interesting parts of Rust worth specifying formally.
> I know people here hate design-by-committee, but historically it's led to some very cohesive languages.
C++ is not cohesive at all
I didn't say this applies to every committee, but I do think the opposite applies to almost every "try it and see" language.
Examples of cohesive languages designed by committees would be Ada and Haskell.
Haskell is anything but cohesive, depending on which feature flags are enabled on GHC, or any other compiler.
Well ok ... experiment but maybe unlike c++ we could have added N keywords removed M keywords for arguably net-simpler language.
Geez I'd hate to be in rust dev shoes if I can't remove something later when I have a better better min/max. I guess this could be done off main, stable.
Rust is also design-by-committee.
Yes, however how many of them are used in production to some level of scale (not even to the scale of Rust)? Stroustrup's quote and all that.
Rust's development process is also design by committee, interestingly enough.
> Rust's development process is also design by committee, interestingly enough.
Sure, but it's still quite informal and they just add things as they go instead of writing a complete standard and figuring out how everything interacts before anything is added to the language. Design-by-committee was probably not the best term to use.
After working with Rust for half a decade I‘m afraid I have to agree. A lot of the newer features have weird edge cases with promises for fixes stuck in bikeshedding hell for years (looking at you const generics). Alternatively feature authors wait on dependecies that will never ship.
You're not wrong here. Not that I'm entirely up-to-speed on all of the deep Rust discussions, but the sense I have of the language evolution is that while there is definitely a loud contingent of people pushing for a lot of the complexity of full effect systems or linear types, these sorts of proposals aren't actually all that likely to actually move forward in the language.
(I should note that of all of the features mentioned in this blog post, the only one I actually expect to see in Rust someday is pattern types, and that's largely because it partially exists already in unstable form to use for things like NonZeroU32.)
Yoshua works directly on developing the language, and mentions he is working on these features specifically (he is part of the effects initiative), I'm not sure you won't see these features in Rust.
Yoshua is part of the "loud contingent" being described. He's not on the lang team, and he's been "working on" things like keyword generics for years without any indication that they are going to make it into the language.
> We (Oli, Niko, and Yosh) are excited to announce the start of the Keyword Generics Initiative, a new initiative 1 under the purview of the language team
https://blog.rust-lang.org/inside-rust/2022/07/27/keyword-ge...
Maybe he's not on the language team (I haven't read enough into Rust governance structures to know definitively) but it's not like he's on some random person working on this. And yes, work takes time, I actually disagreed with his initial approach where his syntax was to have a bunch of effects before the function name, and everyone rightly mentioned how messy it was. So they should be taking it slow anyway.
The thing is that anyone can show up and spend time discussing ideas in Rust project spaces. From the outside it is easy to confuse that with actual movement toward landing changes in the language.
(The communication aspect of this is something that has bothered me many times in the past- even people who are lang team members often phrase things in a way that makes it sound like something is on its way in, when it's still just in the stage of "we're kinda noodling with ideas.")
What complexity?
I agree that the complexity is getting scary. They keep on adding more and more stuff and it is hard to follow.
Could you expand a bit more?
The main problem I see is adding things slowly instead of automatic rewrites.
I remember adding lifetimes in some structs and then wanted to use generics and self pointing with lifetimes because that made sense, and then it didn't work because the composition of some features was not yet part of Rust.
Another thing: there are annotations for lifetimes in function signatures, but not inside the functions where there is a lot of magic happening that makes understanding them and working with them really hard: after finally the borrow checking gave me errors, that's when I just started to getting lots of lifetime errors, which were not shown before.
Rust should add these features but take out the old ones with guaranteed automatic update path.
The edition mechanism covers your last paragraph.
I think part of the idea is you and to make it impossible to misuse an underlying API. This can make development much less complex.
the most exciting thing about rust is that AI agents are good at writing it because its so highly structured
Cool features, what are the chances that's going to happen in the next decade?
These would be great additions. I don't get why people don't want powerful type systems. Let the language work for you!
> In formal terms, Rust’s type system is considered affine, which means that each value must be used at most once. And “use at most once” is exactly what’s needed to guarantee the absence of bugs like “use after free”.
> But there are type systems which can provide even more guarantees. One step beyond “use at most once” is “use exactly once”. Types that provide that guarantee are called “linear” and in addition to guaranteeing the absence of “use after free” they can also guarantee the absence of memory leaks.
Does anyone know why these are called “affine” and “linear”, respectively? What’s the analogy, if any, to the use of those terms in math? (E.g. an affine transformation of vector spaces)
I'm terrified by the notion of try fns. Are we getting exceptions (and therefore losing one of rust's greatest features)?
Isn't his point exactly that we don't want to have too many function colors and instead want a generic way of declaring side effects so people can do what they want (be it try fns, IO, async, etc..., no panicking)?
From the article:
> And on nightly it also has support for try fn and gen fn.
I haven't been able to figure out what a try fn is.
Rustee devs are strange. It feels as if they are on a mission. I don't fully understand it.
I used to hate Golang for not having generics and how verbose getting basic things done was. Then I read posts like this and realise, my god, Rob Pike was so, so right.
Do these people ever ship anything? Or is it just endless rearranging of deckchairs?
I already need a reference to read rust code, looks like I'll need a third reference sheet. The language is currently bordering on spaghetti
The most annoying part is that you can't just go to source code or docs and understand some code. I still can't do it after spending many years using it. You have to wade through 7 layers of macros and traits to understand some basic thing most of the time.
It is easier to understand musl-libc code compared to understanding a http library in rust which is just insane to me.
I mostly agree, however it wouldn't harm Go, even to get back some of the niceties of Limbo that it still misses.
"Because though we’re not doing too bad, we’re no Ada/SPARK yet" ... props to the king. A worthy aspiration. Classy.
async rust is the worse async out there. I prayed that rust did not include a async at all. But the JS devs pushed it thru. That pretty much sealed my rust use. Im still salty.
You know what, I’ve heard people say this and thought “OK, maybe these other languages with GCs and huge runtimes really do something magical to make async a breeze”.
But then I actually tried both TypeScript and C#, and no. Writing correct async code in those languages is not any nicer at all. What the heck is “.ConfigureAwait(false)”? How fun do you really think debugging promise resolution is? Is it even possible to contain heap/GC pressure when every `await` allocates?
Phooey. Rust async is doing just fine.
Try golang, where they did the only sane thing: everything is async from the very beginning, no function colouring
I think practical experience with Go reveals that this choice being “the only sane thing” is highly debatable. It comes with huge drawbacks.
Of course it has drawbacks, everything does, but my practical experience has been hugely in favor of what golang is doing, at least, in terms of cognitive load and code simplicity. It is very much worth it in many, many cases
In .NET 11 C# async management moved to the runtime, mostly eliminating heap allocations and also bringing clean stack traces. You really only need to think about ConfigureAwait(false) when building shared libraries or dealing with UI frameworks (even there you mostly don't need it).
You speak in the past tense, but .NET 11 is not released yet at time of writing. Runtime-async is not the current reality.
True, it's in preview currently, but actually .NET is already very efficient with async today also - https://hez2010.github.io/async-runtimes-benchmarks-2024/ (.NET9 tested here).
Sure, it’s really fine for what it does, but it is not significantly easier to deal with than Rust async, and remains fundamentally unsuited in several scenarios where Rust async works really well.
Unless you are writing GUI code, ConfigureAwait() shouldn't be on your source code.
How does it compare to Kotlin async? I find Kotlin generally hits a good balance across the board.
Not sure who pushed async Rust, but at least async JS does work without having to hunt down for runtimes, and naturally the answer is always Tokio.
Usually the answer is embassy for me.
And embedded systems vastly outnumber classical computers. Every classical computer includes several microcontrollers, every car, modern appliance, camera, toy, etc does too. Safe languages for embedded and OSes is very important. Rust just happens to be pretty good for other use cases too, which is a nice bonus. But that means the language can't be tied to a single prescribed runtime. And that it can't have a GC, etc.
PTC and Aicas would disagree there.
As might microEJ, Meadows, F-Secure, Astrobe and a few others.
> PTC and Aicas would disagree there.
Never heard of either. You will have to expand on your reasoning. Microcontrollers do outnumber classical computers though, that is just a fact. So i don't see why there is anything to disagree about there. Even GPUs have helper microcontrollers for thermal management and other functions.
They have been selling real time bare metal Java runtimes for embedded systems, widely deployed across weapon systems in battleships and missile tracking units, factory automation, satellites, and other similar systems for the last decades.
I bet many of those helper microcontrollers, are still Assembly, compiler specific C, and if there is Rust support, most likely only no_std fits, thus no async anyway.
Java on smartcard etc is a thing. I haven't met anyone who used that and actually like it. And it is apparently nothing like normal java.
Many microcontrollers are indeed still running C, but things are starting to change. Esperif has official support for Rust for example, and other vendors are experimenting with that too. Many other microcontrollers have good community support.
> if there is Rust support, most likely only no_std fits, thus no async anyway.
This is just plain incorrect. The beauty of async in rust is that it does work on no_std. You don't need an allocator even to use Embassy. Instead becuause async tasks are perfectly sized, you can reserve space statically at compile time, you just need to specify with an attribute how many concurrent instances of a given task should be supported.
PTC and Aicas aren't Java on smartcards, they are Java on high integrity computing where human lives might be at stake.
Interesting how compiler specific extensions are ok for C, with a freestanding subset, or Rust no_std, but when it goes to other languages it is no longer the same.
I stand corrected on async Rust then.
> Interesting how compiler specific extensions are ok for C, with a freestanding subset, or Rust no_std, but when it goes to other languages it is no longer the same.
Not sure what you mean here. For Rust there is only one de facto compiler currently, though work is ongoing on gccrs (not to be confused with rustc_codegen_gcc, which only replaces the llvm backend but keeps the rest of the compiler the same). Work is also ongoing on an official spec. But as it currently stands there are no compiler specific extensions.
If you meant the attribute I mentioned for embassy? That is just processed by a rust proc-macro, similar to derives with serde is used to generate (de)serialisation code. It too adds custom attributes on members.
[dead]
Thank God for Zig
For bringing us back to Modula-2?
Elaborate.
I don’t think PL theory driven design produces good systems languages.
Rust as it exists today is very much "PL theory" driven. It's not necessarily a good language, but it's been consistently ranked as the #1 "most loved" by Stack Overflow for the past few years.
The only thing Rust needs to have a chance to succeed is better C++ interoperability. Everything else is a nice to have.
Crafted by Rajat
Source Code