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 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)
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.
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
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.
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.
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.
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.
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.
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.
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.
C++ is not cohesive at all
Examples of cohesive languages designed by committees would be Ada and Haskell.
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.
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.
(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.)
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 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).
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.
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.
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.
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.
"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.
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.
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.
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?
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.
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.
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.
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.
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.
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.
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.
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.
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?
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.
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.
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...
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
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 :)
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.
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.
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.
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
- 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
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.