Rust backward compatibility (upd.)
Posted Feb 02, 2025. Updated Feb 10, 2025 ‐ 5 min read
Promises
Disclaimer: I'm thinking about deleting this article. It nicely shows how naive I am in some situations ;) But OTOH, there may be some urban legend about Rust that lives in the minds of its users, and it's time to fix it.
I remember seeing some opinions that Rust did not deliver backward compatibility promises. Today, I started researching this topic, and it turns out that I was completely wrong.
How did these promises look for me? Here is what I was thinking until today. Basically, if you created a code for Rust 1.0.0, later named the 2015 edition, you were guaranteed that this code should work on any later version of Rust.
There was one caveat here. You didn't have a guarantee that you would have a problem-free migration to the latest edition. Migration to the latest edition of the language is always a half-automated, half-manual process. In other words, you should have a guarantee that your code written for Rust 1.0.0 will work on Rust 1.85.0 using edition 2015. Conversion to edition 2024 will be something that you need to handle.
These guarantees were only for a stable version. If you are using unstable channel and features that are marked as unstable, then you don't have such guarantees.
This was a delusion in which I lived for the past 4 years. I don't know why exactly I had such a delusion. I've read multiple books about Rust and blog posts. Maybe somewhere, I've read something that gave me a wrong perception of reality.
Changelog
Let's have a look at the RELEASES file. If you have a closer look, you will find there are 80 "Compatibility Notes" sections for 118 stable releases (I'm writing this after Rust the 1.84.1 release). You will find there descriptions of multiple issues related to the compiler or language itself that were fixed over the years.
Some of these changes are described as existing code-breaking changes. These code-breaking changes usually have one of the reasons - serious language or compiler flaws.
Please take some time to read descriptions of the issues and look at PRs and RFCs. It shows quite clearly that it's really hard to design a programming language at the level of complication that Rust presents in a few years that would not require changes after the 1.0.0 release.
It is assumed that the design of the Rust language started in 2006. You can check initial commit from rust-prehistory repo. Most of the work was done by Graydon Hoare. Four years later, in 2010 work on the language and compiler was started by a larger group. Rust started to become a different language than Graydon wanted at the beginning. So before its 1.0.0 release in 2015, Rust was designed and developed for 9 years in two different realities. The first one was a language designed and developed by one person, and another was teamwork.
Compiler flaws are another thing. We all know that it's not possible to write a larger flawless program. The software works in reality with different assumptions and requirements. When these assumptions and requirements change once properly working program becomes a buggy program.
We are now fully aware that there were obstacles. Language design problems and compiler bugs. But how these obstacles were handled?
Method
Before making a change that would break the compilation of existing code, Rust compiler developers are adding a warning into a compiler a few releases before making such a change. Thanks to such warning, developers have time to fix their code. A few releases later warning is removed, and a code compilation-breaking change is made.
This situation is not ideal. Developers who don't update the compiler frequently after each release may miss warnings. And they are hit by compilation-breaking changes after they update to a version that contains such changes. I guess because of this problem, some developers may be disappointed that Rust doesn't deliver its promise of stability.
Tools
Rust compiler developers are using two tools that help handle these changes. The first tool is commonly used clippy. Compiler developers are adding lints to clippy that show developers problems in their code and possible solutions. Some of the code changes can be automated. In such a situation it's just a matter of running
cargo clippy --fix
to get an updated version of our code.
Another tool is Crater. It's a powerful tool that allows developers to run tests on all over 170,000 crates from crates.io repository. This allows them to estimate the possible impact of breaking change. If developers see that the impact will be large they can work out some better ways to migrate affected code.
My reality
I've been using Rust on almost a daily basis since the beginning of 2021. Since using version 1.49.0, I have never been hit by a code compilation-breaking change. Why? Maybe my code may be simple enough to not be hit by any change that hits complicated code? Maybe there were no serious breaking changes after 2020? Or maybe I just frequently updated the compiler and fixed warnings?
Despite the fact that I was completely delusional about Rust compatibility guarantees, I never experienced any problem related to code compilation breakage. That shows me one thing. Rust compiler developers handle this problem in a way that is good enough for me. Is it good enough for everyone? Most likely not. That is why some people are complaining.
I can not change how things work. The only thing that I can advise is frequent compiler updates (after each release). Thanks to them, for the past 35 minor version updates, I have not experienced any problems.