- Stabilize never type
- Stabilize custom allocators
- Introduced minimum publish age configurations, also known as dependency cooldowns. When the relevant config is set, the resolver ignores versions published more recently than that, unless they are already in Cargo.lock. (RFC 3923) docs #17335
- cargo-install: now uses the existing lockfile in the .crate tarball by default. --locked in cargo install becomes a no-op. #17388
Long time follower of this. Very grateful it's been implemented.
> warning: unused dependency `tracing-subscriber` --> Cargo.toml:23:1 | 23 | tracing-subscriber.workspace = true | ^^^^^^^^^^^^^^^^^^ | = note: `cargo::unused_dependencies` is set to `warn` by default help: consider removing the dependency on `tracing-subscriber`
Heck yes
The main limitation is in dev-dep detection since there isn't a way to exhaustively run everything that uses dev-deps yet. You can look at logging statements to get an approximation though.
- bool::toggle() https://doc.rust-lang.org/stable/std/primitive.bool.html#met...
- Box::map() https://doc.rust-lang.org/stable/std/boxed/struct.Box.html#m...
- str::trim_prefix(), str::trim_suffix, Path::trim_prefix()
But under the hood it's all the same https://doc.rust-lang.org/stable/src/core/bool.rs.html#157-1...
In effect, if x is a bool, doing x.toggle() is rewritten by the compiler as bool::toggle(&mut x), which can of course modify x.
It’s only weird if you come from languages like Java where “primitives” are distinct from “objects”, which is a self-imposed limitation the language imposes.
Otherwise agreed, methods like this can be mutable in any language that doesn't heavily specialize primitives.
foo.bar().baz[expensive()].qux.toggle();Looks like part of the motivation was for things with long names like `some_struct_a.some_field_b.some_subfield_c`, where needing to name the field twice would have pushed people to using `^= true` instead, which is a bit more roundabout to decipher and risks people forgetting the idiom and writing `^= false` by accident. Not necessarily the most useful API, but not unmotivated and harmless to add.
That said, this release includes some incremental improvements as well as the unused-dependency lint to help reduce wasted work within the ecosystem.
Some changes that have been or are about to be merged for upcoming releases
- We will be reducing the debug info generated for the `dev` profile which reduces build-dir size and speeds up builds. We've split off a `debug` profile for when that is needed.
- Cargo will be deleting the directory rustc writes before each invocation, avoiding the leaking of debug files when split-debug is enabled (default on Mac)
- rlibs for workspace members will no longer be copied from build-dir to target-dir which takes up extra space on systems without hardlinking or where we skip it (like Mac)
- redundant information in rlibs and rmeta's is going to be de-duplicated
Examples of further out changes
- This release includes the foundational work to add a build-dir GC
- Cross-workspace de-duplicating of build-dirs
- Cross-workspace build-dir cache
- Early cut off
“Relink, don't rebuild” #790
And the large target directories is also being worked on as well.
use cargo's --timings option to identify the slow crates, then use measureme to figure where rustc is spending time.
try fiddling with detached debug-info, dwp files can be smaller than their constituent dwo files.
ensure really small "template wrappers" are inlined.
lower the number of compilation-units you use.
For incremental builds I have found it can _sometimes_ be effective to split out larger dependencies into a dynlib. You create a dylib crate that re-exports said dependencies and your executable depends on that. Ofc things like generics from the offending libraries will still end up in your executables; Though I think there is a way to mitigate this a little.
This mostly helps with link-time, and saving space if you have a bunch of executables (using the argv[0] trick will help more, though).
They’re also doing work to reduce the amount of disk space usage (by clearing out unused articacts) and make it easier to maintain.
If your codebase is not some small project, its excruciating slow. Even if you try different linkers, and other compilers, nothing really make incremental compiles eat so much of your system. Even more so if you have multiple AI agents doing security and other checks.
Switched to Go for several months, and the difference was very noticeable. Until you started to get there also 50k+ files that are interlinked. And let us not talk about Go and Rust their issues with insane amount of debug/cache buildup.
Imaging if you have 4 to 6 agents running that, with each seeing the cache grow from 20, 30, 40 ... To the point that a 100/1000GB SSD grows to 800, 900GB usage. Often in barely a day.
Seen my NVME drive its lifespan drop from 98% to 91% in barely a few month time. I dread looking at it again lol
What was the solution? Going back to C ... Despite right now working on a 800k lines project, it compiles way faster the Go, and well, Rust will have pegged all 16 cores for a minute, for EACH agent trying to recompile on a small change.
Memory safety? Instead of checking at compile time, and wasting my time, this has simply been moved to AI agents checking, making tools, checking, at night. So it does not disturb my own work.
Its ironic, how both modern projects, end up being worse off. Best of all, the C project is way faster then the same project in Go and Rust. Yea, it was "fun" having AI transcompile them twice but the difference on actual large codebase, and not some micro benchmarks is not a joke. To the point that now all my projects have gone back to C...
And yea, i expect some fast downvotes by our Rust friends because saying something negative about Rust always gets people downvoted here. No offence but Rust being linked to LLM and the whole checker was a benefit before AI came around. But now you can offload a lot of safety checks to the backend/offhours, instead of paying for it with every compile.
The compilation model is vastly, vastly, vastly better than languages like C++ and C, though, and it does compile much faster than an equivalent C++ build (doesn't count if you download precompiled binaries of your c++ dependencies)
Is Python a fast language because it barely takes any time to compile the code? No obviously not.
> If you could reinvent/change any aspect of Rust, what would you do alternatively?
> A lot happened because we wanted to ship a useful thing with only so much time in the day, so some things were less deliberate than others. There’s an alternate version of Rust with significantly faster compiler times, because while we cared about compile times when designing we always prioritized other values more. We never asked ourselves how this feature or implementation impacted compiler analysis time and there are several spots where something slightly different would have much faster compiler times, which people do care about. But had we spent time on that, we would have neglected other things and not been any more successful.
[1]: https://alexalejandre.com/interviews/interview-with-steve-kl...
This is important, because most often during development you want fast compilation times more than you want fast code.
But some of it is implementation problems.
But that said I do agree it is pretty fast these days. Significantly faster than C++ and people have tolerated that for decades.
Swift handles this through the equivalent of Rust's `dyn`. We could have a stable ABI that's based entirely on `dyn`. Such an ABI would have some limitations, since there are traits `dyn` can't represent (anything that's generic over something other than the type that implements the trait, which is quite common), though we're working on some of those.
Essentially, you wouldn't be able to have a `Vec<T>` in your ABI without either pinning down T or using something like `Vec<Box<dyn Trait>>`. Which is possible, but kind of awful.
We're still planning to ship a stable ABI at some point, but that gives a flavor for some of the kinds of problems we'd need to solve in order for that stable ABI to be useful.
(C++ typically handles generics by having types defined in header files, and those get compiled with the program consuming the library, which is effectively the same situation Rust is in. You could export symbols from a shared library for specific instantiation of a template, but people rarely do that.)
Similar to :
#[rust_abi]
struct MustConform
fn must_conform
So the user must write the shims instead of rust magically build it. This also mean that ABI changes become visible, and changes to rust not mean worries about ABI.Forgive the naive question on something that's probably been discussed many times over, but could there be a middle ground when it comes to a stable ABI and generics? Something where the person building the library for dynamic linking chooses which explicit monomorphizations they'd like to provide for each public generic function (and type), and the compiler stamps out all combinations of those concrete instances? Could the way these are named and stamped out be standardized so that this could be treated as a stable ABI for non-generics?
It seems to me that it would at least cover distros: they could make a pass over all their Rust code and determine which monomorphizations are needed throughout the distro.
https://github.com/joshtriplett/rfcs/blob/crabi-v1/text/3470...
An actual stable native Rust ABI sounds like a forward compatibility nightmare.
If there's a CVE in tokio, the OS could theoretically publish a patched version of tokio and all apps that depend on that end up using the fixed code.
As it stands now, the OS needs to publish updated versions of all rust apps that depend on tokio.
Yes, in that it's primarily a linux thing as MS has gone the route of saving off basically all dlls for all their OSes for backwards compatibility (though they still backpatch).
No, in that other than Rust and Go (and maybe a few others that emit single binaries, zig?) this is how it works with OS distributed programming language packages. Python, ruby, java, perl, C#, the OS will for all of them control distribute and update the libs independent of the apps that use them.
Also, C/C++ still make up a huge percentage of all apps in pretty much any OS.
Uhmm... No? E.g.:
- Python has long strongly recommended virtual environments.
- Not to talk about JavaScript which of course manage things per project by default.
- I don't remember what was the state of things in .NET Framework, but in modern .NET NuGet packages are distributed as DLLs with your app.
I don't know other ecosystems well but I believe they aren't different.
Ummm... yes.
How software is maintained by an OS is different from how software is maintained by 3rd party developers.
- Python has long strongly recommended virtual environments.
Yes, if you are developing a private application. But if that application gets adopted by the OS then you can expect all it's dependencies to be sent to a place like `/usr/lib/python3/dist-packages`.
> Not to talk about JavaScript which of course manage things per project by default.
I didn't bring it up because it's one I'm not familiar with. I expect OS maintainers will do similar things with it that they do with python but I'm not 100% on that.
> I don't remember what was the state of things in .NET Framework, but in modern .NET NuGet packages are distributed as DLLs with your app.
And if your .net application ends up installed as an OS package you can expect those dlls to be stored in /usr/lib.
If you are doing C/C++, as a 3rd party app, you aren't putting the .so files or the .dll files in /usr/lib or C:\Windows\System32. You are storing them in the same directory as your application. But if the OS decides to distribute your app, you can expect your .so files to get put into /usr/lib and even System32 (very very rare for MS to do that, but it does happen when, for example, we are talking about drivers).
Edit: Re: node. Here's the debian guidelines for how dependencies ultimately get shredded and distributed. https://wiki.debian.org/Javascript/Nodejs/Manual?pow_referer...
Stable ABI dramatically improves the problem space there (without necessarily having anything to do with business model).
Hell, even if all exposed functions and types would need to be fully monomorphized at the interface, it would be a lot nicer to pass a `&'a [i64]` rather than a `const * int64_t` together with a `size_t n` and have to marshall the slice on both sides.
A stable Rust ABI would be nice for not just Rust, especially if they refine or extend (formally or de facto) the SysV ABI to optimize passing and returning of discriminants. That could then smooth the way for developing these features in C and C++, such as a primitive error-result type that saw some brief attention in both C and C++ WGs a couple of years ago. Doing it properly--absolute minimum cost, ABI stable, and cross-language--seemed to require too much orchestration across the ecosystem so enthusiasm and ambition seemed to peter out. The work required for a stable Rust ABI could provide the inertia to get that done.
It might make sense to have a subset Swift-style stable ABI, but that's a huge engineering effort, has lots of overhead and is also likely not worth it IMHO.
Why is that? It seems like that makes min-publish-age much less effective.
Any type (like in TypeScript; Rust doesn't have one) is a set of every possible value; a supertype of all types. Every expression regardless of type can be assigned to a variable of type any.
The opposite is the never type, which is an empty set; no value is of type never. Per set theory, an empty set is a subset of every set, which makes the never type a subtype of all types. An expression of type never can be assigned to every variable regardless of type.
If never type has no values, then how can an expression have never type? Easy: it must never evaluate. Infinite loops, unconditional panics/exceptions, process exit/abort, etc. Also, generics - you can have Vec<!> that's always empty, Result<T,!> that can never be an error, etc.
https://blog.rust-lang.org/2026/11/12/Rust-1.100.0/#never-ty...