The Rust project is currently working towards a slate of 17 project goals, with 0 of them designated as Roadmap Goals. This post provides selected updates on our progress towards these goals (or, in some cases, lack thereof). The full details for any particular goal are available in its associated tracking issue on the rust-project-goals repository.
Roadmap goals
Goals looking for help
Other goal updates
| Progress | |
| Point of contact | |
| Champions |
compiler (Oliver Scherer), lang (Tyler Mandry), libs (David Tolnay) |
| Task owners |
3 detailed updates available.
Hi, I'm the new contractor on the interop problem space mapping project goal.
Key developments: What has happened since the last time. It's perfectly ok to list "nothing" if that's the truth, we know people get busy.
In the last week and a half, I've:
- added some draft high-level problem statement summaries
- started mapping out interop use cases
- added relationships between problems/use cases and existing project goals & unstable compiler features
Blockers: List any Rust teams you are waiting on and what you are waiting for.
Nothing at the moment, still working through the high level mapping of the problem space.
Help wanted: Are there places where you are looking for contribution or feedback from the broader community?
Suggestions for more interop use cases would be very welcome, just open a discussion in t-lang/interop and I'll turn it into a ticket. Or go ahead and open a use case ticket directly.
Next step is prioritising a few of the use cases, then working on related problem statements in more detail.
I'll post an update here every few weeks, you can follow more detailed weekly updates on Zulip.
The effort to fill the contracting role to support this project goal is in the process winding down. The interview and discussion process is nearly complete. We expect to make a final decision for the role in early February.
The Rust Foundation is opening up a short-term, approximately 3-month, contracting role to assist in our Rust/C++ Interop initiative. The primary work and deliverables for the role will be to make substantial progress on the Problem Space Mapping Rust Project Goal by collecting discrete problem statements and offering up recommendations on the work that should follow based upon the problems that you found.
If you are interested in how programming languages interoperate, are curious in understanding the problems therein, and are have a passion to think about how those problems may be resolved for the betterment of interop, then this work may be for you.
An ideal candidate will have experience with Rust programming. Having experience in C++ is strongly preferred as well. If you have direct experience with actual engineering that required interoperating between Rust and C++ codebases, that's even better.
If you are interested, please email me (email address found in my GitHub profile) or contact me directly on Zulip by Tuesday, January 27 and we can take it from there to see if there may be a potential fit for further discussion.
Thank you.
| Progress | |
| Point of contact | |
| Champions | |
| Task owners |
7 detailed updates available.
There's been a lot of miscellaneous fixes for mGCA this month. I've also started drafting some blog posts to explain what's going on with mGCA/oGCA as well as soliciting use cases/experience reports for them and adt_const_params. I also talked with some folks at Rust Nation this month about const generics and what features would be useful for them and why.
Latest updates:
Boxy and I have met (and continue to meet) and work on modeling const generics in a-mir-formality. We're still working on laying the groundwork.
There is a proposed project goal for next year: https://rust-lang.github.io/rust-project-goals/2026/const-generics.html
In addition to what niko posted previously there's been a lot of other stuff happening. A lot of people have opened PRs to improve mGCA this month: León Orell Valerian Liehr Noah Lev @enthropy7 Kivooeo @mu001999 @Human9000-bit Redddy @Keith-Cancel @AprilNEA
A rough list of things that have been improved for mGCA:
- Lots of new expressions now supported by mGCA: const constructors, tuple constructor calls, array expressions, tuple expression, literals
- associated_const_equality has been merged into min_generic_const_args. the former was effectively dependent on the latter already so this just makes it nicer to use the former :)
- traits can now be dyn compatible if all associated constants are type consts and are specified in the trait object (e.g.
dyn Trait<ASSOC = 10>) - type consts are enforced to be non-generic
- a bunch of ICEs have been fixed
- camelid has been working on "non-min" version of mGCA which will allow arbitrary expressions to be used in the type system (a blog post with more detail will be published once this actually lands)
In non-mGCA updates, as niko says, we've been meeting regularly to make progress on modelling const generics in a-mir-formality. I've also been spending time thinking about the interactions between adt_const_params and ADTs with privacy/safety invariants and I think I know how to structure the RFC in this area so can make progress on that again
There's some more detail about the various bits of work people have done and who did what here: #project-const-generics > perfectly adequately sized wins @ 💬
Boxy and I have established a regular time to check-in on formalizing this within a-mir-formality. Today we mostly worked on the "model" of const values, starting with this
#![allow(unused)]
fn main() {
#[term]
pub enum ConstData {
// Sort of equivalent to `ValTreeKind::Branch`
#[cast]
RigidValue(RigidConstData),
// Sort of equivalent to `ValTreeKind::Leaf`
#[cast]
Scalar(ScalarValue),
#[variable(ParameterKind::Const)]
Variable(Variable),
}
#[term]
pub enum ScalarValue {
#[grammar(u8($v0))]
U8(u8),
#[grammar(u16($v0))]
U16(u16),
#[grammar(u32($v0))]
U32(u32),
#[grammar(u64($v0))]
U64(u64),
#[grammar(i8($v0))]
I8(i8),
#[grammar(i16($v0))]
I16(i16),
#[grammar(i32($v0))]
I32(i32),
#[grammar(i64($v0))]
I64(i64),
#[grammar($v0)]
Bool(bool),
#[grammar(usize($v0))]
Usize(usize),
#[grammar(isize($v0))]
Isize(isize),
}
#[term($name $<parameters> { $,values })]
pub struct RigidConstData {
pub name: RigidName,
pub parameters: Parameters,
pub values: Vec<Const>,
}
}
i.e., a const value can be a scalar value (as today) or a struct literal like Foo { ... } (which would also cover tuples and things). We got the various tests passing. Huzzah!
Ah another thing I forgot to mention. David Wood spent some time looking into the name mangling scheme for adt_const_params stuff to make sure it would be fine to stabilize and it seems it is so that's another step closer to adt_const_params being stabilizable
Ah I forgot to mention, even though MGCA has a tonne of work left to do I expect it should be somewhat approachable for people to help out with. So if people are interested in getting involved now is a good time :)
Since the last update both of my PRs I mentioned have landed, allowing for constructing ADTs in const arguments while making use of generic parameters. This makes MGCA effectively a "full" prototype where it can now fully demonstrate the core concept of the feature. There's still a lot of work left to do but now we're at the point of finishing out the feature :)
Once again huge thanks to camelid for sticking with me throughout this. Also thanks to errs, oli and lcnr for reviewing some of the work and chatting with me about possible impl decisions.
Some examples of what is possible with MGCA as of the end of this goal cycle:
#![allow(unused)]
#![feature(const_default, const_trait_impl, min_generic_const_args)]
fn main() {
trait Trait {
#[type_const]
const ASSOC: usize;
}
fn mk_array<T: const Default + Trait>() -> [T; T::ASSOC] {
[const { T::default() }; _]
}
}
#![allow(unused)]
#![feature(adt_const_params, min_generic_const_args)]
fn main() {
fn foo<const N: Option<u32>>() {}
trait Trait {
#[type_const]
const ASSOC: usize;
}
fn bar<T: Trait, const N: u32>() {
// the initializer of `_0` is a `N` which is a legal const argument
// so this is ok.
foo::<{ Some::<u32> { 0: N } }>();
// this is allowed as mgca supports uses of assoc consts in the
// type system. ie `<T as Trait>::ASSOC` is a legal const argument
foo::<{ Some::<u32> { 0: <T as Trait>::ASSOC } }>();
// this on the other hand is not allowed as `N + 1` is not a legal
// const argument
foo::<{ Some::<u32> { 0: N + 1 } }>(); // ERROR
}
}
As for adt_const_params we now have a zulip stream specifically for discussion of the upcoming RFC and the drafting of the RFC: #project-const-generics/adt_const_params-rfc. I've gotten part of the way through actually writing the RFC itself though it's gone slower than I had originally hoped as I've also been spending more time thinking through the implications of allowing private data in const generics.
I've debugged the remaining two ICEs making adt_const_params not fully ready for stabilization and written some brief instructions on how to resolve them. One ICE has been incidentally fixed (though more masked) by some work that Kivooeo has been doing on MGCA. The other has been picked up by someone I'm not sure the github handle of so that will also be getting fixed soon.
| Progress | |
| Point of contact | |
| Champions |
compiler (Oliver Scherer), lang (TC) |
| Task owners |
2 detailed updates available.
(Just come back from the Spring Festival)
- In development:
- (locally, no PR yet): design and implement the borrow checking algorithms of
&pin - https://github.com/rust-lang/rust/pull/144537, reviewed, to update the submodule
book - https://github.com/rust-lang/rust/pull/149130, reviewed, to do some refactors according to the reviewed messages.
- (locally, no PR yet): design and implement the borrow checking algorithms of
-
Key developments: forbid manual impl of
Unpinfor#[pin_v2]types. -
Blockers: PRs waiting for review:
- impl
Drop::pin_drop(the submodule issue) - coercion of
&pin mut|const T<->&[mut] T
- impl
-
Help wanted: None yet.
| Progress | |
| Point of contact | |
| Champions | |
| Task owners |
- At the beginning of December, we set out to answer five important questions regarding the virtual places approach. We discussed four questions and arrived at answers for three.
- The first question we looked at was question 3 Canonical Projections.
- Next we looked at question 4 Non-Indirected Containers.
- As the final question we answered, we looked at question 1 Field-by-Field Projections vs One-Shot Projections.
- At the moment, we are investigating question 2 and I wrote a blog post with a potential solution that still needs feedback.
- We started a Wiki Project to consolidate our knowledge in one place.
- We implemented an algorithm to determine the type of a place expression.
- Our plan is to continue this project goal in the next goal period.
6 detailed updates available.
The first pull request of the lang experiment has just been merged: rust-lang/rust#152730
This PR enables the use of the field_of! macro to obtain a unique type for each field of a struct, enum variant, tuple, or union. We call these types field representing types (FRTs). When the base type is a struct that is not repr(packed), only contains Sized fields, this type automatically implements the Field trait that exposes some information about the field to the type system. The offset in bytes from the start of the struct, the type of the field and the type of the base type.
The feature is still incomplete and highly experimental. We also want to tackle the limitations in future PRs. For the moment this is enough to give us the ability to experiment with library versions of field projections and write functions that are generic over the fields of structs. For example one can write code like this:
#![feature(field_projections)]
use std::field::{Field, field_of};
use std::ptr;
fn project_ref<'a, T, F: Field<Base = T>>(r: &'a T) -> &'a F::Type {
// SAFETY: the `Field` trait guarantees that this is sound.
unsafe { &*ptr::from_ref(r).byte_add(F::OFFSET).cast() }
}
struct Struct {
field: i32,
other: u32,
}
fn main() {
let s = Struct { field: 42, other: 24 };
let r = &s;
let field = project_ref::<_, field_of!(Struct, field)>(r);
let other = project_ref::<_, field_of!(Struct, other)>(r);
println!("field: {field}"); // prints 42
println!("other: {other}"); // prints 24
}
A very important feature of the types returned by field_of! is that you can implement traits for them if you own the base type. This allows anointing fields with information by extending the Field trait. For example, this allows encoding the property of being a structurally pinned field:
#![allow(unused)]
fn main() {
use std::pin::Pin;
unsafe trait PinnableField: Field {
type StructuralRefMut<'a>
where
Self::Type: 'a,
Self::Base: 'a;
fn project_mut<'a>(base: Pin<&'a mut Self::Base>) -> Self::StructuralRefMut<'a>
where
Self::Type: 'a,
Self::Base: 'a;
}
fn project_pinned<'a, T, F>(r: Pin<&'a mut T>) -> <F as PinnableField>::StructuralRefMut<'a>
where
F: PinnableField<Base = T>,
{
F::project_mut(r)
}
}
We can then implement this extra trait for all of the fields of our struct (and automate that with a proc-macro):
#![allow(unused)]
fn main() {
unsafe impl PinnableField for field_of!(Struct, field) {
type StructuralRefMut<'a> = &'a mut i32;
fn project_mut<'a>(base: Pin<&'a mut Self::Base>) -> Self::StructuralRefMut<'a>
where
Self::Type: 'a,
Self::Base: 'a,
{
let base = unsafe { Pin::into_inner_unchecked(base) };
&mut base.field
}
}
unsafe impl PinnableField for field_of!(Struct, other) {
type StructuralRefMut<'a> = Pin<&'a mut u32>;
// u32 is `Unpin`, so this isn't doing anything special, but it highlights the pattern.
fn project_mut<'a>(base: Pin<&'a mut Self::Base>) -> Self::StructuralRefMut<'a>
where
Self::Type: 'a,
Self::Base: 'a,
{
let base = unsafe { Pin::into_inner_unchecked(base) };
unsafe { Pin::new_unchecked(&mut base.other) }
}
}
}
Now you can safely obtain a pinned mutable reference to other and a normal mutable reference to field by calling the project_pinned function and supplying the correct FRT.
Earlier this month, [@Nadrieril][] Ding Xiang Fei and I held a meeting on autoref and method resolution in a world with field projections. This meeting resulted in a new page for the wiki on autoref.
Wiki Project
We started a wiki project at https://rust-lang.github.io/beyond-refs to map out the solution space. We intend to grow it into the single source of truth for the current state of the field projection proposal as well as unfinished and obsolete ideas and connections between them. Additionally, we will aim to add the same kind of information for the in-place initialization effort, since it has overlap with field projections and, more importantly, has a similarly large solution space.
In the beginning you might find many stub pages in the wiki, which we will work on making more complete. We will also mark pages that contain old or abandoned ideas as such as well as mark the current proposal.
This issue will continue to receive regular detailed updates, which are designed for those keeping reasonably up-to-date with the feature. For anyone out of the loop, the wiki project will be a much better place when it contains more content.
Field-by-Field Projections vs One-Shot Projections
We have used several different names for these two ways of implementing projections. The first is also called 1-level projections and the second multi-level projections.
The field-by-field approach uses field representing types (FRTs), which represent a single field of a struct with no indirection. When writing something like @x.y.z, we perform the place operation twice, first using the FRT field_of!(X, y) and then again with field_of!(T, z) where T is the resulting type of the first projection.
The second approach called one-shot projections instead extends FRTs with projections, these are compositions of FRTs, can be empty and dynamic. Using these we desugar @x.y.z to a single place operation.
Field-by-field projections have the advantage that they simplify the implementation for users of the feature, the compiler implementation and the mental model that people will have to keep in mind when interacting with field projections. However, they also have pretty big downsides, which either are fundamental to their design or would require significant complification of the feature:
- They have less expressiveness than one-shot projections. For example, when moving out a subsubfield of
x: &own Structby doinglet a = @x.field.a, we have to move outfield, which prevents us from later writinglet b = @x.field.b. One-shot projections allow us to track individual subsubfields with the borrow checker. - Field-by-field projections also make it difficult to define type-changing projections in an inference friendly way. Projecting through multiple fields could result in several changes of types in between, so we would have to require only canonical projections in certain places. However, this requires certain intermediate types for which defining their safety invariants is very complex.
We additionally note that the single function call desugaring is also a simplification that also lends itself much better when explaining what the @ syntax does.
All of this points in the direction of proceeding with one-shot projections and we will most likely do that. However, we must note that the field-by-field approach might yield easier trait definitions that make implementing the various place operations more manageable. There are several open issues on how to design the field-by-field API in the place variation (the previous proposal did have this mapped out clearly, but it does not translate very well to places), which would require significant effort to solve. So at this point we cannot really give a fair comparison. Our initial scouting of the solutions revealed that they all have some sort of limitation (as we explained above for intermediate projection types for example), which make field-by-field projections less desirable. So for the moment, we are set on one-shot projections, but when the time comes to write the RFC we need to revisit the idea of field-by-field projections.
Non-Indirected Containers
Types like MaybeUninit<T>, Cell<T>, ManuallyDrop<T>, RefCell<T> etc. currently do not fit into our virtual places model, since they don't have an indirection. They contain the place directly inline (and some are even repr(transparent)). For this reason, we currently don't have projections available for &mut MaybeUninit<T>.
Enter our new trait PlaceWrapper which these types implement in order to make projections available for them. We call these types place wrappers. Here is the definition of the trait:
#![allow(unused)]
fn main() {
pub unsafe trait PlaceWrapper<P: Projection<Source = Self::Target>>: HasPlace {
type WrappedProjection: Projection<Source = Self>;
fn wrap_projection(p: P) -> Self::WrappedProjection;
}
}
This trait should only be implemented when Self doesn't contain the place as an indirection (so for example Box must not implement the trait). When this trait is implemented, then Self has "virtual fields" available (actually all kinds of place projections). The name of these virtual fields/projections is the same as the ones of the contained place. But their output type is controlled by this trait.
As an example, here is the implementation for MaybeUninit:
#![allow(unused)]
fn main() {
impl<T, P: Projection<Source = T>> PlaceWrapper<P> for MaybeUninit<T> {
type WrappedProjection = TransparentProjection<P, MaybeUninit<T>, MaybeUninit<P::Target>>;
fn wrap_projection(p: P) -> Self::WrappedProjection {
TransparentProjection(p, PhantomData, PhantomData)
}
}
}
Where TransparentProjection will be available in the standard library defined as:
#![allow(unused)]
fn main() {
pub struct TransparentProjection<P, Src, Tgt>(P, PhantomData<Src>, PhantomData<Tgt>);
impl<P: Projection, Src, Tgt> Projection for TransparentProjection<P, Src, Tgt> {
type Source = Src;
type Target = Tgt;
fn offset(&self) -> usize {
self.0.offset()
}
}
}
When there is ambiguity, because the wrapper and the wrapped types both have the same field, the wrapper's field takes precedence (this is the same as it currently works for Deref). It is still possible to refer to the wrapped field by first dereferencing the container, so x.field refers to the wrapper's field and (*x).field refers to the field of the wrapped type.
Canonical Projections
We have discussed canonical projections and come up with the following solution:
#![allow(unused)]
fn main() {
pub trait CanonicalReborrow: HasPlace {
type Output<'a, P: Projection<Source = Self::Target>>: HasPlace<Target = P::Target>
where
Self: PlaceBorrow<'a, P, Self::Output<'a, P>>;
}
}
Implementing this trait permits using the syntax @$place_expr where the place's origin is of the type Self (for example @x.y where x: Self and y is an identifier or tuple index, or @x.y.z etc). It is desugared to be:
#![allow(unused)]
fn main() {
@<<Self as CanonicalReborrow>::Output<'_, projection_from_place_expr!($place_expr)>> $place_expr
}
(The names of the trait, associated type and syntax are not final, better suggestions welcome.)
Reasoning
- We need the
Outputassociated type to support the@x.ysyntax forArcandArcRef. - We put the FRT and lifetime parameter on
Outputin order to force implementers to always provide a canonical reborrow, so if@x.aworks, then@x.balso works (whenbalso is a field of the struct contained byx).- This (sadly or luckily) also has the effect that making
@x.aand@x.breturn different wrapper types is more difficult to implement and requires a fair bit of trait dancing. We should think about discouraging this in the documentation.
- This (sadly or luckily) also has the effect that making
| Progress | |
| Point of contact | |
| Champions |
bootstrap (Jakub Beránek), lang (Niko Matsakis), spec (Pete LeVasseur) |
| Task owners |
Pete LeVasseur, Contributors from Ferrous Systems and others TBD, |
2 detailed updates available.
Key developments: We have a Project Goal in 2026 that we'll take on: Stabilize FLS Release Cadence. Progress towards 1.93.1 looks good, most issues are closed. Blockers: None currently Help wanted: We'd love more folks from the safety-critical community to contribute to picking up issues or opening an issue if you notice something is missing.
Meeting notes here: FLS team meeting 2025-12-12
Key developments: We're close to completing the FLS release for 1.91.0, 1.91.1. We've started to operate as a team, merging a PR with the changelog entries, then opening up issues for each change required: ✅ #624(https://github.com/rust-lang/fls/issues/624), ✅ #625(https://github.com/rust-lang/fls/issues/625), ✅ #626(https://github.com/rust-lang/fls/issues/626), ⚠️ #623(https://github.com/rust-lang/fls/issues/623). #623(https://github.com/rust-lang/fls/issues/623) is still pending, as it requires a bit of alignment with the Reference on definitions and creation of a new example. Blockers: None currently Help wanted: We'd love more folks from the safety-critical community to contribute to picking up issues or opening an issue if you notice something is missing.
| Progress | |
| Point of contact | |
| Champions | |
| Task owners |
3 detailed updates available.
We just posted our February status update for BorrowSanitizer. TL;DR:
-
We provide detailed error messages for aliasing violations, which look almost like Miri's do!
-
We have two forms of retag intrinsic:
__rust_retag_memand__rust_retag_reg. We no longer require a compiler plugin to determine the permission associated with a retag, which will make it possible to use BorrowSanitizer by providing a single-Zsanitizer=borrowflag to rustc. You can check out our MCP for more detailed design updates. -
We are starting to have a better understanding of how BorrowSanitizer performs in practice, but we do not have enough data yet to be certain. From one test case, it seems like we are somewhat faster but still in the same category of performance as Miri when we compare against other sanitizers. Expect more detailed results to come as we scale up our benchmarking pipeline.
-
We have a tentative plan for upstreaming BorrowSanitizer in 2026, starting with its LLVM components. We intend to start the RFC process on the LLVM side this spring, once our API is stable.
Here's our January status update!
-
Yesterday, we posted an MCP for our retag intrinsics. While that's in progress, we'll start adapting our current prototype to remove our dependence on MIR-level retags. Once that's finished, we'll be ready to submit a PR.
-
We published our first monthly blog post about BorrowSanitizer.
-
Our overall goal for 2026 is to transition from a research prototype to a functional tool. Three key features have yet to be implemented: garbage collection, error reporting, and support for atomic memory accesses. Once these are complete, we'll be able to start testing real-world libraries and auditing our results against Miri.
Here's our December status update!
-
We have revised our prototype of the pre-RFC based on Ralf Jung's feedback. Now, instead of having two different retag functions for operands and places, we emit a single
__rust_retagintrinsic in every situation. We also track interior mutability precisely. At this point, the implementation is mostly stable and seems to be ready for an MCP. -
There's been some discussion here and in the pre-RFC about whether or not Rust will still have explicit MIR retag statements. We plan on revising our implementation so that we no longer rely on MIR retags to determine where to insert our lower-level retag calls. This should be a relatively straightforward change to the current prototype. If anything, it should make these changes easier to merge upstream, since they will no longer affect Miri.
-
BorrowSanitizer continues to gain new features, and we've started testing it on our first real crate (lru) (which has uncovered a few new bugs in our implementation). The two core Tree Borrows features that we have left to support are error reporting and garbage collection. Once these are finished, we will be able to expand our testing to more real-world libraries and confirm that we are passing each of Miri's test cases (and likely find more bugs lurking in our implementation). Our instrumentation pass ignores global and thread-local state for now, and it does not support atomic memory accesses outside of atomic
loadandstoreinstructions. These operations should be relatively straightforward to add once we've finished higher-priority items. -
Performance is slow. We do not know exactly how slow yet, since we've been focusing on feature support over benchmarking and optimization. This is at least partially due to the lack of garbage collection, based on what we're seeing from profiling. We will have a better sense of what our performance is like once we can compare against Miri on more real-world test cases.
As for what's next, we plan on posting an MCP soon, now that it's clear that we will be able to do without MIR retags. You can expect a more detailed status update on BorrowSanitizer by the end of January. This will discuss our implementation and plans for 2026. We will post that here and on our project website.
| Progress | |
| Point of contact | |
| Champions | |
| Task owners |
No detailed updates available.
| Progress | |
| Point of contact | |
| Champions | |
| Task owners |
Taylor Cramer, Taylor Cramer & others |
1 detailed update available.
Current status:
- The RFC for
auto implsupertraits has been updated to address SemVer compatibility issues. - There is a parsing PR kicking off an experimental implementation. The tracking issue for this experimental implementation is here.
| Progress | |
| Point of contact | |
| Champions |
compiler (Manuel Drehwald), lang (TC) |
| Task owners |
Manuel Drehwald, LLVM offload/GPU contributors |
2 detailed updates available.
Key developments:
std::autodiff is moving closer to nightly, and std::offload is gaining various performance, feature, and hardware support improvements.
autodiff
Jakub Beránek, @sgasho, and I continued working on enabling autodiff in nightly. We have a PR up that builds autodiff in CI, and verified that the artifacts can be installed and work on Linux. For apple however, we noticed that any autodiff usage hangs. After some investigation, it turns out that we ended up embedding two LLVM copies, one in rustc, and one in Enzyme. It should be comparably easy to get rid of the second one. Once we verified that this fixes the build, we'll merge the PR to enable autodiff on both targets in nightly.
offload
A lot of interesting updates on the performance, feature, and hardware support side.
-
Marcelo Domínguez, @kevinsala, @jdoerfert, and I started implementing the first benchmarks, since that's generally the best way to find missing features or performance issues. We were positively surprised by how good the out-of-the-box performance was. We will implement a few more benchmarks and post the results once we have verified them. We also implemented multiple PRs which implement bugfixes, cleanups, and needed features like support for scalars. We also started working on LLVM optimizations which make sure that we can achieve even better performance.
-
I noticed that our offload intrinsic allowed running Rust code on the GPU, but it wasn't of much help when calling gpu vendor libraries like cuBLAS. In https://github.com/rust-lang/rust/pull/150683 I implemented a new helper intrinsic which allows calling those functions conveniently, without having to manually move data to or from the device. It will benefit from the same LLVM optimizations as our full offload intrinsic. It also a bit simpler to set up on the compiler and linker side, so it already works with
stdand mangled kernel names, something that we still have to improve for our main offload intrinsic. -
A lot of work happened on the LLVM offload side for SPIRV and Intel GPU support. At the moment, our Rust frontend is tested on NVIDIA and AMD server and consumer GPUs, as well as AMD HPC and Lapotop APUs. Karol Zwolak reached out since he wants to help with with also running Rust on Intel GPUs. Offload relies on LLVM which started gaining Intel support, so hopefully we won't need much work beyond a new intel-gpu target and a new stdarch module. There is also work on a new spirv target for rustc, which we could also support if it goes through LLVM. Due to some open questions around typed pointers it does not seem clear yet whether it will, so we will have to wait.
-
Nikita started working on updating our submodule to LLVM 22. This hopefully does not only brings some compile and runtime performance improvements, but also greatly simplifies how we can build and use offload. Once it landed I'll refactor our bootstraping logic, and as part of that start building offload in CI.
Time for the next round of updates. Again, most of the updates were on the GPU side, but with some notable autodiff improvements too.
autodiff:
-
@sgasho finished his work on using dlopen to load enzyme and the pr landed. This allowed Jakub Beránek and me to start working on distributing Enzyme via a standalone component.
-
As a first step, I added a nicer error if we fail to find or dlopen our Enzyme backend. I also removed most of our autodiff fallbacks, we now unconditionally enable our macro frontend on nightly: https://github.com/rust-lang/rust/pull/150133 You may notice that
cargo expandnow works on autodiff code. This also allowed the first bug reports about ICE (internal compiler error) in our macro parser logic. -
Kobzol opened a PR to build Enzyme in CI. In theory, I should have been able to download that artifact, put it into my sysroot, and use the latest nightly to automatically load it. If that had worked, we could have just merged his PR, and everyone could have started using AD on nightly. Of course, things are never that easy. Even though both Enzyme, LLVM, and rustc were built in CI, the LLVM version shipped along with rustc does not seem compatible with the LLVM version Enzyme was built against. We assume some slight cmake mismatch during our CI builds, which we will have to debug.
offload:
-
On the gpu side, Marcelo Domínguez finished his cleanup PR, and along the way also fixed using multiple kernels within a single codebase. When developing the offload MVP I had taken a lot of inspiration from the LLVM-IR generated by clang - and it looks like I had gotten one of the (way too many) LLVM attributes wrong. That caused some metadata to be fused when multiple kernels are present, confusing our offload backend. We started to find more bugs when working on benchmarks, more about the fixes for those in the next update.
-
I finished cleaning up my offload build PR, and Oliver Scherer reviewed and approved it. Once the dev-guide gets synced, you should see much simpler usage instructions. Now it's just up to me to automate the last part, then you can compile offload code purely with cargo or rustc. I also improved how we build offload, which allows us to build it both in CI and locally. CI had some very specific requirements to not increase build times, since our x86-64-dist runner is already quite slow.
-
Our first benchmarks directly linked against NVIDIA and AMD intrinsics on llvm-ir level. However, we already had an nvptx Rust module for a while, and since recently also an amdgpu module which nicely wraps those intrinsics. I just synced the stdarch repository into rustc a few minutes ago, so from now on, we can replace both with the corresponding Rust functions. In the near future we should get a higher level GPU module, which abstracts away naming differences between vendors.
-
Most of my past rustc contributions were related to LLVM projects or plugins (Offload and Enzyme), and I increasingly encountered myself asking other people for updates or backports of our LLVM submodule, since upstream LLVM has fixes which were not yet merged into our LLVM submodule. Our llvm working group is quite small and I didn't want to burden them too much with my requests, so I recently asked them to join it, which also got approved. In the future I intend to help a little with the maintenance here.
| Progress | |
| Point of contact | |
| Champions |
lang (Josh Triplett), lang-docs (TC) |
| Task owners |
3 detailed updates available.
Updates from the 2026-01-28 and 2026-02-11 meetings:
Removing the likely/unlikely hints in favour of cold_path
The stabilization of core::hint::cold_path lint is imminent and after it, the likely and unlikely hints are likely (pardon the pun) to be removed.
The team discussed the impact of this. These hints are used in C but not yet in Rust. cold_path would be sufficient, but likely/unlikely would still be more convenient in cases where there isn't an else branch. Tyler Mandry mentioned that these can be implemented in terms of cold_path.
Niche optimizations
We discussed the feasibility of embedding data in lower bits of a pointer -- something the kernel is doing in C. This could also enable setting the top bit in the integers (which is otherwise never set) and make it reprent an error in that case (and a regular pointer otherwise).
Ideally, this would be done in safe Rust, as the idea is to improve the safety of the C code in question.
Extending the niches is something Rust wants to see, but it's waiting on pattern types. There are short/medium-term options by using unsafe and wrapping it in a safe macro, but the long-term hope is to have this supported in the language.
Vendoring zerocopy
The project has interest in vendoring zerocopy. We had its maintainers Jack Wrenn and Joshua Liebow-Feeser join us to discuss this and answer our questions. The main question was about whether to vendor at all, how often should we (or will have to) upgrade, and how much of it is expected to end up in the standard library.
The project follows semver with the extended promise to not break minor versions even before 1.0.0. We could vendor the current 0.8 and we should be upgrade on our own terms (e.g. when we bring in new features) rather than being forced to.
Right now, the project is able to experiment with various approaches and capabilities. Any stdlib integration a long way away, but there is interest in integrating these to the language and libraries where appropriate.
New trait solver
There's been a long-term effort to finish the new trait solver, which will unblock a lot of things. Niko Matsakis asked about things it's blocking for Rust for Linux.
This is the list: unmovable types, guaranteed destructors, Type Alias Impl Trait (TAIT), Return Type Notation (RTN), const traits, const generics (over integer types), extern type.
2026 Project goals
This year brings in the concept of roadmaps. We now have a Rust for Linux and a few more granular Goals. We'll be adding more goals over time, but the one merged cover what we've been focusing on for now.
Update from the 2026-01-14 meeting.
Deref / Receiver
Ding's arbitrary_self_types: Split the Autoderef chain rust#146095 is waiting on reviews. It updates the method resolution to essentially: deref_chain(T).flat_map(|U| receiver_chain(U)).
The perf run was a wash and a carter has completed yesterday. Analysis pending.
RFC #3851: Supertrait Auto-impl
Ding has submitted a Rust Project goal for Supertrait Auto Impl.
Arbitrary Self Types rust#44874
We've discovered the #[feature(arbitrary_self_types_pointer)] feature gate. As the Lang consensus is to not support the Receiver trait on raw pointer types we're probably going to remove it (but this needs further discussion). This was a remnant from the original proposal, but the Lang has changed direction since.
derive(CoercePointee) rust#123430
Ding is working on a fix to prevent accidental specialization of the trait implementation. rust#149968 is adding an interim fix.
Alice opened a Reference PR for rust#136776. There are questions around the behaviour of the as cast vs. coercions.
Pass pointers to const in assembly rfc#3848
Gary opened implementation for the RFC: rust#138618.
In-place initialization goal#395
Ding is writing a post to describe all the open proposals including Alice's new one that she brouhght up during the LPC 2025. He'll merge it in: https://rust-lang.github.io/beyond-refs.
Field Projections goal#390
Benno updated the Field Representing Types PR to the latest design. This makes the PR much simpler.
Tyler opened a wiki to keep all the proposals, resources in one place: https://rust-lang.github.io/beyond-refs.
Macros, attributes, derives, etc.
Josh brought up his work on adding more capable declarative macros for writing attributes and derives. He's asked the Rust for Linux team for what they need to stop using proc macros.
Miguel noted they've just added dependency on syn, but they would like to remove it some day if their could.
Benno provided a few cases of large macros that he thought were unlikely to be replacable by declarative-style ones. Josh suggested there may be a way and suggested an asynchronous discussion.
Quick bit of great news: Rust in the Linux kernel is no longer treated as an experiment, it's here to stay 🎉
https://lwn.net/SubscriberLink/1050174/63aa7da43214c3ce/
| Progress | |
| Point of contact | |
| Champions | |
| Task owners |
Benno Lossin, Alice Ryhl, Michael Goulet, Taylor Cramer, Josh Triplett, Gary Guo, Yoshua Wuyts |
1 detailed update available.
A proposal to continue this goal in the next goal period was merged: https://github.com/rust-lang/rust-project-goals/pull/477
| Progress | |
| Point of contact | |
| Champions | |
| Task owners |
1 detailed update available.
The RFC draft was reviewed in detail and Ralf Jung pointed out that the proposed semantics introduce issues because they rely on "no-behavior" (NB) with regards to choosing an address for a local. This can lead to surprising "time-traveling" behavior where the set of possible addresses that a local may have (and whether 2 locals can have the same address) depends on information from the future. For example:
#![allow(unused)]
fn main() {
// This program has DB
let x = String::new();
let xaddr = &raw const x;
let y = x; // Move out of x and de-initialize it.
let yaddr = &raw const y;
x = String::new(); // assuming this does not change the address of x
// x and y are both live here. Therefore, they can't have the same address.
assume(xaddr != yaddr);
drop(x);
drop(y);
}
#![allow(unused)]
fn main() {
// This program has UB
let x = String::new();
let xaddr = &raw const x;
let y = x; // Move out of x and de-initialize it.
let yaddr = &raw const y;
// So far, there has been no constraint that would force the addresses to be different.
// Therefore we can demonically choose them to be the same. Therefore, this is UB.
assume(xaddr != yaddr);
// If the addresses are the same, this next line triggers NB. But actually this next
// line is unreachable in that case because we already got UB above...
x = String::new();
// x and y are both live here.
drop(x);
drop(y);
}
With that said, there is still a possibility of achieving the optimization, but the scope will need to be scaled down a bit. Specifically, we would need to:
- no longer perform a "partial free"/"partial allocation" when initializing or moving out of a single field of a struct. The lifetime of a local starts when any part of it is initialized and ends when it is fully moved out.
- allow a local's address to change when it is re-initialized after having been fully moved out, which eliminates the need for NB.
This reduces the optimization opportunities since we can't merge arbitrary sub-field moves, but it still allows for eliminating moves when constructing a struct from multiple values.
The next step is for me to rework the RFC draft to reflect this.
| Progress | |
| Point of contact | |
| Champions | |
| Task owners |
2 detailed updates available.
Key developments
PR open to get the first working version of the Reborrow and CoerceShared traits merged.
Blockers
Currently "blocked" on PR review, and of course my (and Ding's) work to fix all review issues.
The review has brought up an opportunity to replace Rvalue::Ref / ExprKind::Ref with a more generalised variant that could encompass both references and user-defined references. This would be powerful, but it would be a very big and scary change. If this turns out to be a blocking issue for reviewers, then this will block the goal for the foreseeable future as the PR then starts on a massive refactoring.
Help wanted
The PR currently does not include derive traits, but we'd really want them. Instead of these:
#![allow(unused)]
fn main() {
impl<'a> Reborrow for CustomMarker<'a> {}
impl<'a> CoerceShared<CustomMarkerRef<'a>> for CustomMarker<a'> {}
impl<'a, T> Reborrow for CustomMut<'a, T> {}
impl<'a, T> CoerceShared<CustomRef<'a, T>> for CustomMut<'a, T> {}
}
we'd prefer to have something like this:
#![allow(unused)]
fn main() {
#[derive(Reborrow, CoerceShared(CustomMarkerRef))]
struct CustomMarker<'a> { ... }
#[derive(Reborrow, CoerceShared(CustomRef))]
struct CustomMut<'a, T> { ... }
}
If anyone feels like picking up this thread, that'd be awesome: the derive macros do not need to really perform any validity checking, as the trait itself will do that.
If the PR merges soon, then public testing and exploration of the traits will be the next big thing. Likely concurrently with that the massive refactoring to generalise Rvalue::Ref / ExprKind::Ref.
Purpose
A refresher on what we want to achieve here: the most basic form of reborrowing we want to enable is this:
#![allow(unused)]
fn main() {
// Note: not Clone or Copy
#[derive(Reborrow)]
struct MyMutMarker<'a>(...);
// ...
let marker: MyMarkerMut = MyMutMarker::new();
some_call(marker);
some_call(marker);
}
ie. make it possible for an owned value to be passed into a call twice and have Rust inject a reborrow at each call site to produce a new bitwise copy of the original value for the passing purposes, and mark the original value as disabled for reads and writes for the duration of the borrow.
A notable complication appears with implementing such reborrowing in userland using explicit cals when dealing with returned values:
#![allow(unused)]
fn main() {
return some_call(marker.reborrow());
}
If the borrowed lifetime escapes through the return value, then this will not compile as the borrowed lifetime is based on a value local to this function. Alongside convenience, this is the major reason for the Reborrow traits work.
CoerceShared is a secondary trait that enables equivalent reborrowing that only disables the original value for writes, ie. matching the &mut T to &T coercion.
Update
We have the Reborrow trait working, albeit currently with a bug in which the marker must be bound as let mut. We are working towards a working CoerceShared trait in the following form:
#![allow(unused)]
fn main() {
trait CoerceShared<Target: Copy> {}
}
Originally the trait had a type Target ADT but this turned out to be unnecessary, as there is no reason to particularly disallow multiple coercion targets. The original reason for using an ADT to disallow multiple coercion targets was based on the trait also having an unsafe method, at which point unscrupulous users could use the trait as a generic coercion trait. Because the trait method was found to be unnecessary, the fear is also unnecessary.
This means that the trait has better chances of working with multiple coercing lifetimes (think a collection of &muts all coercing to &s, or only some of them). However, we are currently avoiding any support of multiple lifetimes as we want to avoid dealing with rmeta before we have the basic functionality working.
| Progress | |
| Point of contact | |
| Champions |
compiler (Oliver Scherer), lang (Scott McMurray), libs (Josh Triplett) |
| Task owners |
oli-obk |
3 detailed updates available.
- @BD103 added slices, arrays and raw pointer support
- https://github.com/rust-lang/rust/pull/151019
- https://github.com/rust-lang/rust/pull/151031
- https://github.com/rust-lang/rust/pull/151118
- https://github.com/rust-lang/rust/pull/151119
- Asuna added all of our primitives
- https://github.com/rust-lang/rust/pull/151123
- Jamie Hill-Daniel gave us references
- https://github.com/rust-lang/rust/pull/151222
- @izagawd made it possible to extract some info from dyn Trait
- https://github.com/rust-lang/rust/pull/151239
There is ongoing work for Adts and function pointers, both of which will land as MVPs and will need some work to make them respect semver or generally become useful in practice
Removing the 'static bound from try_as_dyn turned out to have many warts, so I'm limiting it to a much smaller subset and will have borrowck emit the 'static requirement if the other rules do not apply (instead of having an unconditional 'static requirement)
- https://github.com/rust-lang/rust/pull/146923 has landed, and we even got the first contribs adding array support to reflection.
- there are lots more types and type information that we could support, and it's rather easy to add more. Happy to review any work here.
- https://github.com/rust-lang/rust/pull/150033 has landed, and I'm working on removing the
'staticrequirement in https://github.com/rust-lang/rust/pull/150161
Updates
- https://github.com/rust-lang/rust/pull/148820 adds a way to mark functions and intrinsics as only callable during CTFE
- https://github.com/rust-lang/rust/pull/144363 has been unblocked and just needs some minor cosmetic work
Blockers
- https://github.com/rust-lang/rust/pull/146923 (reflection MVP) has not been reviewed yet
| Progress | |
| Point of contact | |
| Champions |
cargo (Ed Page), lang (Josh Triplett), lang-docs (Josh Triplett) |
| Task owners |
3 detailed updates available.
Key developments
- FCP has ended on frontmatter support, just awaiting merge (https://github.com/rust-lang/rust/pull/148051)
- Cargo script has entered FCP (https://github.com/rust-lang/cargo/pull/16569)
Blockers
- Potential issues around edition, see https://github.com/rust-lang/rust/issues/152254
Key developments
- #146377 has been decided and merged
Blockers
- T-lang discussing CR / text direction feedback: https://github.com/rust-lang/rust/pull/148051#issuecomment-3638326490
- T-rustdoc deciding on and implementing how they want frontmatter handled in doctests
Key developments
- A fence length limit was added in response to T-lang feedback (https://github.com/rust-lang/rust/pull/149358)
- Whether to disallow or lint for CR inside of a frontmatter is under discussion (https://github.com/rust-lang/rust/pull/149823)
Blockers
- https://github.com/rust-lang/rust/pull/146377
- rustdoc deciding on and implementing how they want frontmatter handled in doctests
| Progress | |
| Point of contact | |
| Champions |
compiler (David Wood), lang (Niko Matsakis), libs (Amanieu d'Antras) |
| Task owners |
5 detailed updates available.
Progress has been slow since the last update because I've been busy, but I've been working on a rebase of rust-lang/stdarch#1509, which has bitrot quite a bit. Rémy Rakic is joining me to work on the Sized Hierarchy parts of the goal.
rust-lang/rust#143924 has been merged, enabling scalable vector types to be defined on nightly, and I'm working on a patch to introduce unstable intrinsics/scalable vector types to std::arch
Update to the previous post.
Tyler Mandry pointed me at this thread, where lcnr posted this nice blog post that he wrote detailing more about (C).
Key insights:
- Because the use of
size_of_valwould still cause post-mono errors when invoked on types that are notSizeOfVal, you know that addingSizeOfValinto the function's where-clause bounds is not a breaking change, even though adding a where clause is a breaking change more generally. - But, to David Wood's point, it does mean that there is a change to Rust's semver rules: adding
size_of_valwould become a breaking change, where it is not today.
This may well be the best option though, particularly as it allows us to make changes to the defaults across-the-board. A change to Rust's semver rules is not a breaking change in the usual sense. It is a notable shift.
Update: David and I chatted on Zulip. Key points:
David has made "progress on the non-Sized Hierarchy part of the goal, the infrastructure for defining scalable vector types has been merged (with them being Sized in the interim) and that'll make it easier to iterate on those and find issues that need solving".
On the Sized hierarchy part of the goal, no progress. We discussed options for migrating. There seem to be three big options:
(A) The conservative-but-obvious route where the T: Derefin the old edition is expanded to T: Deref<Target: SizeOfVal> (but in the new edition it means T: Deref<Target: Pointee>, i.e., no additional bounds). The main downside is that new Edition code using T: Deref can't call old Edition code using T: Deref as the old edition code has stronger bounds. Therefore new edition code must either use stronger bounds than it needs or wait until that old edition code has been updated.
(B) You do something smart with Edition.Old code where you figure out if the bound can be loose or strict by bottom-up computation. So T: Deref in the old could mean either T: Deref<Target: Pointee> or T: Deref<Target: SizeOfVal>, depending on what the function actually does.
(C) You make Edition.Old code always mean T: Deref<Target: Pointee> and you still allow calls to size_of_val but have them cause post-monomorphization errors if used inappropriately. In Edition.New you use stricter checking.
Options (B) and (C) have the downside that changes to the function body (adding a call to size_of_val, specifically) in the old edition can stop callers from compiling. In the case of Option (B), that breakage is at type-check time, because it can change the where-clauses. In Option (C), the breakage is post-monomorphization.
Option (A) has the disadvantage that it takes longer for the new bounds to roll out.
Given this, (A) seems the preferred path. We discussed options for how to encourage that roll-out. We discussed the idea of a lint that would warn Edition.Old code that its bounds are stronger than needed and suggest rewriting to T: Deref<Target: Pointee> to explicitly disable the stronger Edition.Old default. This lint could be implemented in one of two ways
- at type-check time, by tracking what parts of the environment are used by the trait solver. This may be feasible in the new trait solver, someone from @rust-lang/types would have to say.
- at post-mono time, by tracking which functions actually call
size_of_valand propagating that information back to callers. You could then compare against the generic bounds declared on the caller.
The former is more useful (knowing what parts of the environment are necessary could be useful for more things, e.g., better caching); the latter may be easier or more precise.
I haven't made any progress on Deref::Target yet, but I have been focusing on landing rust-lang/rust#143924 which has went through two rounds of review and will hopefully be approved soon.
| Progress | |
| Point of contact | |
| Champions | |
| Task owners |
1 detailed update available.
RFC has been accepted. I'm preparing a 2026 continuing goal for stabilization.