Code doesn’t stop running on existing hardware when the language changes in a future compiler. You can still use the same old toolchain.
I’ve done a lot of embedded development in a past life. Keeping old tool chains around for each old platform was standard.
I would much rather go through the easy process of switching to an older Rust tool chain to build something than all of the games we played to keep entire VMs archived with a snapshot of a vendor tool chain that worked to build something.
They had some great stories trying to get something or other running again where they had to fly one of the original designers over to hand solder a board back into action.
How we do that today is a bit of an interesting problem I don’t think they’ve convincingly solved; basically maintaining nightly builds forever — a couple 1U’s of kubernetes in deep storage ain’t gonna do it, you’re not gonna be able to solder a xeon back to life..
I know I’d rather be trying get a load of c99 rebuilt for some mips or other after 20 years that some random version of rust.
Rust 1.0 is 11 years old and it's still trivial to compile Rust code from then. I doubt that will change in the next 9 years.
C is an absolute nightmare in comparison. I tried to compile some old C code I had for Nordic nRF51 chips, only a few years after the chips became available. I gave up. Broken links, missing documentation, etc. etc. I can see why other people here are saying it's standard practice to archive a VM. Not really necessary for Rust.
Maybe it's trivial to compile Rust code but it's not trivial to build a project with dependencies. I'm trying to get my feet wet with an official USB example project from Embassy on my RP2040. It doesn't work in the latest git repo for some unknown reason (might be my fault, probably is, but it's not obvious to me).
I'm assuming it worked at some point, maybe something changed and someone forgot to update something somewhere (there are lots of example projects). So I thought I'd "git bisect" until I find a working version and go from there. Well, I cannot get it to build against anything older than a year ago and that version also isn't working for me. It's dependency and Rust edition hell.
https://github.com/sshine/dikumud/commits/master/
It wasn't plug-and-play.
My guess is that when Rust code gets 30 years old, the problem would more likely be that you can't find an already compiled compiler that will work for that old code, and that the compilers themselves need bootstrapping. So you'll just fast-forward the code to work on a new compiler instead.
If it's pure Rust code then the latest Rust compiler will almost certainly work.
The issue will be if some of the crates wrap C code... but that kind of proves the point.
Sure, just vendor them in, simples, right?
An exception is for crates that wrap C code which might get the code from elsewhere but those are quite rare.
So uh, will these ever make it to an auction site you think?
I like OpenVMS and am slowly learning more about it; no reason to wait until you see those hit eBay :-)
If the toolchain moves on, the product is suck on whatever architecture and developers are stuck running an emulator/docker of some particular vintage of Debian sid.
If you keep your old computer around, yes.
The good news is that C seems also contaminated with "move fast, break things " phylosophy. The modern code writer is not able to make things that last more than a couple of months.
Unless you find out the compiler was buggy and was producing faulty binaries, but the new compiler can no longer compile the old code.
The truth is: If the toolchain was good enough to ship your product, has time to go EOL, and then you do a patch that surfaces an esoteric toolchain bug, then the odds are that you'll know exactly what triggered the bug and you can work around it by writing different code.
Because even if the newer shinier compiler/toolchain had the issue fixed, most companies wouldn't upgrade to it at that point. It's almost never desirable to change your toolchain for a shipping product, you're just introducing more unknowns.
This reaction to toolchain stability is quite defensive, and was needed for C, but isn't universally needed. C toolchain updates could break your product because of how loose the C language can be; I've had code that had benign undefined behaviour, until a toolchain update brought in an optimisation that broke it.
Another outcome of a toolchain update could be "no bugs introduced, existing bugs in your codebase now found by diagnostics".
It's easy to install different rust toolchains. You could increment the toolchain version forward and find the next toolchain to include the fix, or even backport the fix to a custom toolchain if you want.
The comments acting like Rust is breaking code all the time are also pretty lost. I've been developing Rust since +/- the 1.0 days and this isn't a common occurrence. When something does need to change it's usually a tightening up of something that was incorrect in the past, and it's easy to fix.
Some of these comments act like everything is going to collapse at any moment and the old code will be unusable, which is pretty ridiculous
Now I’ve not extensively used Rust but almost everytime I did it ended up needing nightly to use some library or other.
To be fair it's not even a lower bound, as using a stable compiler doesn't imply the absence of nightly only feature (as in Cargo features, the ones you can enable on crates you depend on).
In my opinion what matters is the functionality. If it's provided by a nightly-only crate or as a nightly-only feature of an otherwise non-nightly-only crate it doesn't really matter.
But I agree that this is become more and more rare.
I'm curious why I've seen this sentiment repeated in so many places, I learned Rust once 5 years ago and I haven't had to learn any new idioms and there have been no backwards incompatible changes to it that required migrating any of my code.
This attitude works in carpentry, but not in software. You need to get proficient, but your tools will keep evolving, like everything else in the software world.
You can, in fact, learn C exactly once. Or any number of other languages. The entire argument being made here is that the world you're suggesting is a problem. Software developers should not have to continually relearn their tools and it is abnormal to suggest they should.
I'll take C written by people who've learned and improved since then any & every day of the week.
- a lot of code now uses mix of witness types and const generics
- with new borrow checker release they will do new iterators 2.0
Seems like coding on 5 year old Rust is like C++ 98.
L1: The edition system allows Rust to literally mutate the language. 2024 edition (if you begin a new Rust project today) has different rules from 2021 Edition, from 2018 edition and the Rust 1.0 "2015 edition". These changes aren't exactly huge, but they are real and at corporate scale you would probably want to add say a one day internal seminar to learn what's new in a new edition if you want to adopt that edition. For example we hope 2027 edition will swap out the 1..=10 syntax to be sugar for the new core::range::RangeInclusive<i32> not today's core::ops::RangeInclusive<i32> and this swap delivers some nice improvements.
L2: Unlike C++ the Rust stdlib unconditionally grows for everybody in new compiler releases. So even if you stuck with 2015 Edition, all the time since Rust 1.0, when you use a brand new Rust compiler you get the standard library as it exists today in 2026, not how it was in 2015 when you began coding. If you decided you needed a "strip_suffix" method for the string slice reference type &str you might have written a Rust trait, say, ImprovedString and implemented it for &str to give it your strip_suffix method. Meanwhile in Rust 1.45 the Rust standard library &str also gained a method for the same purpose with the same name and so now what you've written won't compile due to ambiguity. You will need to modify your software to compile it on Rust 1.45 and later.
L3: Because Rust is a language with type inference, changes to what's possible which seem quite subtle and of no consequence for existing code may make something old you wrote now ambiguous because what once had a single obvious type is now ambiguous. This is more surprising than the L2 case because now it seems as though this should never have compiled at all. Type A and B already existed, before it inferred type A, now it insists B might be possible, but it may be quite a tangle to discover why B was not a possibility until this new version of Rust. If the compiler had rejected your code when you wrote it in 2015 as ambiguous you'd have grunted and written what you meant, but at this distance in time it may be hard to remember, did you mean B here?
Now the nonsense: There's a vague superstition that Rust is constantly changing while good old C is absolutely stable. Neither is true by orders of magnitude. If you really need certainty you should freeze actual hardware and software, or at the very least build a VM and then nothing changes because you changed nothing. If you'd have been comfortable upgrading to a new CC version, you shouldn't be scared about upgrading the Rust tools.
Switch to Rust 1.50 and now it's calling the stdlib strip_suffix silently, I actually wasn't expecting it to be silent, and obviously if they have the same exact behaviour (mine instead panics to show we're calling it) you wouldn't even notice, but it is a change.
But on the other hand, it could be a bug as the trait resolver is commonly mentioned as the buggiest part of the language. I'm scared of the breakage if they fix it though.
So we didn't bring str::strip_prefix from the prelude in preference to our custom trait, we made a string literal and those have type &'static str -- an immutable reference to a string which lives forever. So the "prelude doesn't win" rule does not apply for &str because it didn't come from the prelude.
If we were talking about a type which implements Iterator for example, new Iterator features would come from Iterator, which is in the prelude and you didn't specifically ask for Iterator so the things you did ask for beat Iterator. But here the language primitive type grew new methods, a thing which Rust does but many languages don't do - Rust has methods on pointers and bytes and anything, whereas a language like Java or C++ can only put methods on "classes" not the ordinary types.
I thought the builtins were defined in core and reexported by the prelude (they are defined in core, they're just implicitly in scope anyway).
But I still think expected behaviour is that builtins should have the same precedence as the prelude.
Compare to [this](https://play.rust-lang.org/?version=stable&mode=debug&editio...)
Where's the problem exactly?
Might not matter for a lot of embedded, but if you are doing something like exposing functionality via a webserver or something that would be network-connected, then security updates in third-party libraries may be important.
For example, it would be really easy for me to run old code that's pinned to something like Python 3.7, but if libraries have updated to Python 3.x without backwards compatibility, then I'm stuck using the out of date versions or just backporting myself.
That statement deserves support.
The Rust compiler can build a 2024 edition application which depends on a 2015 edition library, which in turn depends on a 2018 edition library.
Every crate can upgrade at their own pace, or even never at all.
Editions exist to avoid breaking code over time.
- a project with no Cargo.lock, where there have been breaking changes in a dependency that wasn't specific enough in Cargo.toml; fixing this requires some finessing of dependencies but is possible to get the project building without any code changes
- a project with proper dependency tree specified, but where a std change cause inference to break specific older versions of a crate in your tree (time 0.35 comes to mind); this requires similar changes to the above
- a project relies on UB on stable code that should always have been disallowed and since fixed; this is tricky, on a dependency, an updated version will likely exist, on your own project you'd have to either change your code or use the older toolchain, knowing that the code might not be doing what you want it to do (this happened a handful of times pre 1.20)
- an older project, with the proper dependency versions specified, being built on a newer platform; I saw this with someone trying to build a project untouched since 2018 on an ARM Mac: the toolchain for it didn't exist back then, and the macOS specific lib they were using didn't have any knowledge either. Newer versions of the library do, of course, but that required updating a set of libs that would be compatible too.
All of these cases are quite rare. You could encounter all of them at the same time, and that would be annoying, enough to have someone doing it for fun say "fuck it" and drop it. You can also get hit by a lightning.
But between Cargo.lock which should allow your project to build on newer toolchains, and access to all prior toolchains, your project should continue to build forever on the same platform.
Rustc does have fairly frequent (every ~18 months of so) minor breaking changes between versions. These are often related to type inference, usually only affect a very small number of crates, and are usually mitigated by publishing patch versions of those crates that don't run into the issue. But if you have the patch version locked with a lockfile then that won't help you, and there is increased likelihood of the build failing, so it's best to lock down the rustc version too.
Luckily pinning the rustc version is very easy to do.
---
On regular projects this kind of issue can usually also be fixed by upgrading to the latest rustc and running `cargo update`. But conservative embedded projects may have legitimate reasons for not wanting to upgrade rustc to the latest version, and parts of ecosystem's disregard for MSRVs means that running `cargo update` on an older rustc has a high chance of causing build breakage due to MSRV issues.
All of these options require you to go out of your way to enable breakage.
You could also be in the super unlucky state of using something that was later proved unsound in std, which is the only case where rust will break your code on stable. (Missused unsafe in std)
So, it can happen in any programming language, and to any large projects.
Rust allows me to handle this easily with rust.toolchain file, so, this concern is kinda overblown imo
For Rust code for serious industrial use cases or firmwares, it's always best to minimize dependencies as much as possible to avoid this. Making local copies of dependencies is also a thing for certain use cases.
Rust on the contrary incentivises using dependencies, and especially embedded software is hard to write without using external packages (e.g. cortex-m-rt, bytemuck and many others)
imo it's just so much easier
Another is the complexity of the language when it comes to low-level programming. E.g. bytemuck I've mentioned before solves a problem that is hard to even explain to a C developer.
Of course it was always understood that you could have bugs in C libraries and some of them may include memory unsafety, but the culture is very different when there's no explicit way to demarcate the parts of the code most deserving of scrutiny.
Build against the lockfile to use the same versions.
Unless they were pulled from upstream, they won’t suddenly stop building against the same compiler version. Rustup makes it easy to switch compiler versions to get back to the same one you used, too.
Vendoring is the only solution to this but it's really discouraged in rust-land and there is no first-party support for it. You can kind of manually vendor your deps with cargo, and there are third party tools. But compare that to go-land where `go mod vendor` gets you 95-100% of the way there.
No, the lesson from left-pad that every centralized package manager learned was that you cannot allow users to remove uploaded packages at their leisure. All outright code removal can only be done manually by the admins themselves, and it's unlikely to happen outside of some legal compulsion.
> Vendoring is the only solution to this but it's really discouraged in rust-land and there is no first-party support for it.
This is completely incorrect. Cargo ships with `cargo vendor` out of the box, it's neither discouraged nor unsupported by first-party tools: https://doc.rust-lang.org/cargo/commands/cargo-vendor.html
As for the broader crate ecosystem, if crates you depend on drop support for APIs you depend on, that could cause you to get stuck on older unsupported releases. Though that is no different of a problem than any other language.
[1] https://www.joelonsoftware.com/2002/01/06/fire-and-motion/
Not counting compiler specific extensions from GCC, clang, Microsoft, Intel, NVidia, AMD, IBM, Oracle, Apple, Green Hills, TI, Microbit, Mikroe, and many more C compiler vendors that could have been used to compile a specific project during the last 50 years.
We have Rust code in a living code base that is more than 5 years old and it's required maybe one touch in the last 5 years to fix some issues due to stricter rules. It was simple enough it could have been automated.