-
Notifications
You must be signed in to change notification settings - Fork 1.6k
RFC: Const self fields #3888
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
RFC: Const self fields #3888
Conversation
|
An alternative to this would be that we make existing associated constants object-safe. Currently, traits with Proposed Semantics:
Benefits over
|
I did consider going this route at first, but after deeper thought, I thought it was best that we needed something new. let us assume a trait Foo { const AGE: i32; }Knowing how rust works, type |
|
FWIW, trait "fields" is a concept that has been proposed in the past but never had too much traction to get off the ground. I do think that effort on constant "fields" should probably work together with that. |
|
@clarfonthey Trait fields and this RFC are very different though. This RFC is only about per-implementation constant metadata stored in the vtable, not per-instance data or layout/borrowing semantics. Because we can’t currently add new entries to the vtable or change trait object metadata from a library, there’s no macro or workaround that can reproduce the performance benefits here (a direct metadata load instead of an indirect function call). I’m happy to frame this as a narrow “const metadata fields” subset that a future trait fields design could absorb, but it seems orthogonal enough that we don’t have to solve full trait fields first. |
|
I have two questions:
Note that these are mutually incompatible. Since that would amount to putting Cell in a global and allowing mutations to it. |
Just like how rust deals with attempted interior mutability mutations in const variables today. You can not mutate them. Try this code use std::sync::Mutex;
const MUTEX_NUM: Mutex<u32> = Mutex::new(0);
*MUTEX_NUM.lock().unwrap() += 5;
println!("{}", MUTEX_NUM.lock().unwrap());It will still print out 0. You just can not mutate const variables, even if they have interior mutability. since
Yes. As stated in the RFC, if you have trait Foo {
const self FOO : i32;
}You can get it's reference, and it will be considered a static reference fn work_with_dyn_foo(input: &dyn Foo) {
let reference: &'static i32 = &input.FOO;
}EDIT: Just realized the compiler does not stop you from putting |
These are contradictory statements when interior mutability is involved. But I think this answers my question. Currently for consts you can only get a It should be possible to do that for |
|
@RustyYato ok this is news to me lmao. I have taken note of that. Yeah, I would have to add a |
text/3888-const-self-fields.md
Outdated
| `const self` declarations: | ||
| * Can only be assigned to constant expressions. | ||
| * Are per concrete type (for inherent impls) or per (Trait, ConcreteType) pair for trait implementations. | ||
| * The type `T` of a `const self` field must be `Sized`, `Freeze`, and `'static` to prevent unsoundness. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that this shouldn't require Freeze here, that should only be required to take a reference to a const self field.
It should be fine to use these fields by value, that's just like using a normal const by value.
|
I think the fundamental conflict here is whether this thing should behave like a value or a place.
And a big problem with the So here's an idea: a single attribute that can be applied to either a |
|
I think theemathas suggestion is nice, and it makes me think about having this syntax. Thoughts? Value only:
place:
usage let variable = obj.X; //ok. Copies it
let variable2 = &obj.X; // ok, but what it actually does is copy it, and uses the reference of the copy
let variable3 = obj.Y; // ok if the type of Y impls Copy
let variable4 = &obj.Y; // ok |
Adjusted ambiguity rules a bit
|
If anyone has any issues with how it works now, I am always willing to hear feedback. |
text/3888-const-self-fields.md
Outdated
|
|
||
|
|
||
| `static const self` is similar to `const self`, however, working on `static const self` fields means working directly with its reference. Think: global `static` variable you are not allowed to mutate. | ||
| This means that the type of a `static const self` field must not have any interior mutability. In other words, the type of the field must implement `Freeze`. This is enforced by the compiler. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
statics can't be generic. Allowing them in traits would incorrectly bypass this restriction given that trait impls can be generic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct me if I’m wrong, but my understanding is that Rust forbids generic statics because the compiler can’t guarantee a single, unique memory location for each instantiation. With monomorphization, the “same” generic static could end up duplicated in multiple codegen units, each with a different address
If that is the case, I did document that you cannot rely on static const self in having unique memory addresses.
Is that a bad design, or should I make it more clear?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that's a bad design. Part of being a static is having a single unique location in memory. That's why it is okay to allow them to be mutable (static mut or interior mutable static). We should definitely not allow anything to be mutable without a guarantee about its location in memory. And if we don't allow it to be mutable in any way, it can just be a const.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
static might not have been the best name then, since static does imply a fixed memory location. Someone did recommend it to be called const self ref
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That sounds like it's just a constant of reference type, and doesn't have to be a separate language feature.
text/3888-const-self-fields.md
Outdated
| # Future possibilities | ||
| [future-possibilities]: #future-possibilities | ||
|
|
||
| * Faster type matching than `dyn Any`: Since `dyn Any` does a virtual call to get the `TypeId`, using `static const self` to store the `TypeId` would be a much more efficient way to downcast. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be a bit more efficient. I don't think the perf difference would be all that much on modern cpus, especially when you also take the typeid comparison itself into account.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just did benchmarks. Since static const self is not actually a feature in rust, I simulated it by creating structs that were similar to it. These were the results:
dyn_typeid_eq: ~1.15 ns
static_const_self_typeid_eq: ~0.32 ns
the static const self equivalent seems to be more than triple times more performant than the dyn_typeid equivalent
Version details:
rustc 1.93.0-nightly (6647be936 2025-11-09)
binary: rustc
commit-hash: 6647be93640686a2a443a49f15c3390b68c8b5dd
commit-date: 2025-11-09
host: x86_64-pc-windows-msvc
release: 1.93.0-nightly
LLVM version: 21.1.3
My CPU is: AMD Ryzen 7 9800X3D if that helps
code:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is a fair bit more than I would have expected.
|
I think that there isn't a need to split this feature into This should still allow you to take 'static references of your vtable impl. I think a |
|
@RustyYato The way I could see it work is: if the Also btw |
|
Oh wait, on second thought @RustyYato I remembered why I agreed with @theemathas about this. In Rust, const is fundamentally “value substitution”: it means “copy the value and use it”, not “there is a unique storage location with this address”. In that sense, a const doesn’t conceptually have a memory address. When we write: const SOME_I32: i32 = 5;
let reference: &'static _ = &SOME_I32;this is essentially behaving like: let reference: &'static _ = &5;The compiler decides to promote that temporary to a With trait objects, a That code is rejected because using |
|
If you wrap it in a const block, you can still get a 'static reference. https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=162f9612df52864a1e7a3a84ff781200 |
|
I believe that this is what it is essentially doing Since working with if we did allow: let reference : &'static _ = &obj.CONST_FIELD;it would be surprising that using a |
|
Wouldn't a const static field as you specify it in this RFC be equivalent to having a const field that has a reference type? |
We cannot. The following code compiles in current rust: const X: i32 = 1;
fn main() {
let a: &'static (i32, i32) = &(X, 2);
}It is not feasible to get the exact same behavior with this RFC. It might be possible to modify the rules of const promotion to limit what exactly is allowed with this RFC. However, const promotion is already extremely complicated, so that seems like a headache to me. |
I don't think so. A const field that's a reference would store a pointer in the vtable that points to the relevant value. A const static field would store the relevant value directly in the vtable. |
|
maybe a good option is to use trait MyTrait {
const self A: Foo;
// ref means you get a reference when you access it, but the vtable still contains Bar directly
const self ref B: Bar;
fn f(&self);
}
fn demo(v: &dyn MyTrait, foo: fn(Foo), bar: fn(&'static Bar)) {
foo(v.A);
bar(v.B);
}which is equivalent to (ignoring struct MyTraitVTable {
size: usize,
align: usize,
drop: fn(*mut ()),
A: Foo,
B: Bar,
f: fn(*const ()),
}
fn demo(v_data: *const (), v_vtable: &'static MyTraitVTable, foo: fn(Foo), bar: fn(&'static Bar)) {
foo(ptr::read(&v_vtable.A)); // copied (via ptr::read) just like normal `const`
bar(&v_vtable.B); // you get a reference since it was declared ref
} |
That is semantically equivalent. The latter is merely an optimization over the former. |
|
It's not even clear that it is an optimization. vtables can be duplicated, and having a copy of some large value in every one of them might not be great. We may want a pointer indirection anyway. (That also simplifies the implementation since it keeps all the vtable slots at pointer size.) |
FWIW, there have been prior discussions about dropping the requirement that |
Associated consts and const self are different, I believe. let us assume associated const with trait objects did exist, and we are going to use the if we have a collection of with |
Why would that have to be the case? |
Oh nvm. I kind of imagined assoc consts with trait objects as But still, dropping the requirement that dyn Trait implements Trait, I can't imagine how that would work. |
Larger types would indeed lead to more binaries, but I do not see why it's a bad idea to give a developer more options to work with. Sometimes a developer might be willing to take that binary size increase to avoid the extra pointer chase. |
| struct Foo; | ||
|
|
||
| impl Foo { | ||
| const self CONST_FIELD: u32 = 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it’s pointless to allow const self fields on inherent impls (except maybe for macros?), so I’d favor only allowing them on trait impls (unless someone can convince me otherwise).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I only added them in inherent trait impls because usually, items available in traits are also available in inherent implementations. I am indifferent with this decision, so I can just remove it if most people think its unnecessary
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think keeping it for inherent impls for consistency would be nice
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For reference, associated types are currently allowed in stable rust only in trait impls.
I don't see any reason to allow const self in inherent impls.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For reference, associated types are currently allowed in stable rust only in trait impls.
afaict that's mostly because of a limitation of the trait solver, rather than than Rust deciding we don't want associated types in inherent impls. it is available as a nightly feature, though iirc it's incomplete and buggy.
I don't see any reason to allow
const selfin inherent impls.
if you want to have things on your struct that act like fields (so you can do my_struct.some_field) but aren't actually stored in your struct, this is a good way (though it doesn't match the preferred name casing). abusing Deref also works in some cases.
This RFC proposes const self fields: per-type constant metadata that can be accessed through values and trait objects using expr.FIELD syntax.
For trait objects, implementations store their constant data inline in the vtable, allowing &dyn Trait to read per-impl constants (like flags, versions) without a virtual function call. For non trait objects, it is as efficient as accessing a constant value.
This makes patterns like “methods that just return a literal” both more expressive and more efficient, especially in hot loops over many trait objects.
Rendered