Skip to content

Conversation

@izagawd
Copy link

@izagawd izagawd commented Nov 26, 2025

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

@CryZe
Copy link

CryZe commented Nov 26, 2025

An alternative to this would be that we make existing associated constants object-safe.

Currently, traits with const items are not dyn-compatible (Error E0038). If we simply implemented the backend logic proposed in this RFC (storing constants in the vtable), we could lift this restriction for all associated constants.

Proposed Semantics:

  • Continue using const NAME: Type; in traits.
  • If the trait is used as a trait object, the compiler emits the constant into the vtable.
  • Allow obj.NAME syntax. If obj is dyn Trait, it performs a vtable lookup. If obj is concrete, it resolves to the static value (sugar for T::NAME). Though the latter isn't strictly necessary, just a nice to have for consistency.

Benefits over const self:

  • Users don't have to choose between two kinds of constants definitions, they are just constants.
  • Existing traits with constants immediately become dyn-compatible.
  • Solves the motivation without expanding the language surface area.

@izagawd
Copy link
Author

izagawd commented Nov 26, 2025

An alternative to this would be that we make existing associated constants object-safe.

Currently, traits with const items are not dyn-compatible (Error E0038). If we simply implemented the backend logic proposed in this RFC (storing constants in the vtable), we could lift this restriction for all associated constants.

Proposed Semantics:

* Continue using `const NAME: Type;` in traits.

* If the trait is used as a trait object, the compiler emits the constant into the vtable.

* Allow `obj.NAME` syntax. If `obj` is `dyn Trait`, it performs a vtable lookup. If `obj` is concrete, it resolves to the static value (sugar for `T::NAME`). Though the latter isn't strictly necessary, just a nice to have for consistency.

Benefits over const self:

* Users don't have to choose between two kinds of constants definitions, they are just constants.

* Existing traits with constants immediately become dyn-compatible.

* Solves the motivation without expanding the language surface area.

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 dyn Foo implements Foo. That would mean that <dyn Foo>::AGE should be valid. But that can’t really work, because the bare type dyn Foo doesn’t have any metadata / vtable attached. We only get that once we have an actual trait object. It cannot know the value of AGE because the context given: <dyn Foo>::AGE, does not involve an underlying type.

@ehuss ehuss added the T-lang Relevant to the language team, which will review and decide on the RFC. label Nov 26, 2025
@clarfonthey
Copy link

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.

@izagawd
Copy link
Author

izagawd commented Nov 26, 2025

@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.

@RustyYato
Copy link

RustyYato commented Nov 26, 2025

I have two questions:

  • How would this work with interior mutability? I.e. would i be able to store a Cell<u32> in a const self field?.
  • Can you take references to const self fields? (I.e. a reference directly into the vtable)

Note that these are mutually incompatible. Since that would amount to putting Cell in a global and allowing mutations to it.

@izagawd
Copy link
Author

izagawd commented Nov 26, 2025

@RustyYato

How would this work with interior mutability? I.e. would i be able to store a Cell<u32> in a const self field?.
Just like how rust deals with interior mutability in const today. try running this code

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 Cell<u32> can't be in const variables, (compiler won't let us do that due to thread stuff), I used Mutex<u32> instead .
So yes, you would be able to store something like a Mutex (not Cell. compiler stops you), but you can't mutate it.

Can you take references to const self fields? (I.e. a reference directly into the vtable)

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 Cell<T> in const variables, but it still doesnt let you mutate it anyways

@RustyYato
Copy link

Just like how rust deals with attempted interior mutability mutations in const variables today. You can not mutate them.

You can get it's reference, and it will be considered a static reference

These are contradictory statements when interior mutability is involved. But I think this answers my question.

Currently for consts you can only get a 'static reference when you have T: Freeze (no direct interior mutability). Otherwise what happens is you get a reference to a temporary. That's why your mutex example looks like no mutation is happening. And why you can get a 'static reference to a i32.

It should be possible to do that for const self fields. Could you add some text clarifying this to the RFC?

@izagawd
Copy link
Author

izagawd commented Nov 26, 2025

@RustyYato ok this is news to me lmao. I have taken note of that. Yeah, I would have to add a Freeze requirement. Thanks for letting me know.

`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.

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.

@theemathas
Copy link

I think the fundamental conflict here is whether this thing should behave like a value or a place.

  • Value semantics
    • More intuitive with fn-like syntax
    • Will create a new "copy" of the value each time you use it, even for non-Copy types
    • Allows interior mutability
  • Place semantics
    • More intuitive with static-like syntax
    • Will give you a 'static place each time you refer to it, although it might be (de)duplicated at compile time
    • Doesn't allow interior mutability

And a big problem with the const-like syntax is that normal consts sometimes behave like a value and sometimes like a place, due to const promotion.

So here's an idea: a single attribute that can be applied to either a static or a fn. Let the user choose which semantics they want.

@izagawd
Copy link
Author

izagawd commented Nov 27, 2025

I think theemathas suggestion is nice, and it makes me think about having this syntax. Thoughts?

Value only:

const self X: Type = value

place:

static const self Y: OtherType = value

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

@izagawd
Copy link
Author

izagawd commented Nov 27, 2025

If anyone has any issues with how it works now, I am always willing to hear feedback.



`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.
Copy link
Member

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.

Copy link
Author

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?

Copy link
Member

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.

Copy link
Author

@izagawd izagawd Nov 29, 2025

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

Copy link
Member

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.

# 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.
Copy link
Member

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.

Copy link
Author

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:

https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=d85bae34e59fd2ca008bbeff72254917

Copy link
Member

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.

@RustyYato
Copy link

RustyYato commented Nov 28, 2025

I think that there isn't a need to split this feature into static const self. Especially since normal statics are guaranteed to have a single address. I think just folding them together is better. Especially since we can specify that the reference behviour is the same as normal consts.

This should still allow you to take 'static references of your vtable impl.

I think a static self should only be introduced if it guarantees a single address for the value. Otherwise it introduces a footgun when combined with Mutex/RwLock/etc.

@izagawd
Copy link
Author

izagawd commented Nov 28, 2025

@RustyYato The way I could see it work is:

if the const self field's type implements Frozen, you can get a 'static reference without the fear of undefined behavior. If not, you can't and it will always work with a copy. This will indeed make it work similar to normal const variables. If this is what we go with, then right off the top of my head I do not see any issues with this. @theemathas What do you think?

Also btw static const self does enforce the field's type implements Frozen, so it won't even let you use Mutex/RwLock in the first place.

@izagawd
Copy link
Author

izagawd commented Nov 28, 2025

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 'static allocation, but that’s because there would be no issues with it, not because that is what const variables are supposed to do if we go by its fundamental definition

With trait objects, a const self value isn’t known at compile time in the same way. The actual value depends on which concrete type’s vtable you end up with at runtime. We could model &obj.FIELD as a reference into vtable metadata, but that runs into the same kind of issues as in this example:

https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=1b20ea1029f954495dcf6ba17df02048

That code is rejected because const is supposed to behave like “copy/substitute then use”, and allowing a 'static reference there would effectively promote a value with Drop into immortal global state (its destructor would never run). That breaks the mental model of const and is probably why the compiler is conservative about when it allows promotion to 'static.

using static const self, for instance, will enable us to store things that needs drop, and at the same time, enable us to get their 'static references.

@RustyYato
Copy link

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

@izagawd
Copy link
Author

izagawd commented Nov 28, 2025

@RustyYato

I believe that this is what it is essentially doing

https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=677ae063208965882b18ca507c8e2be8

Since working with const self means potentially working with trait objects, the const { &obj.FIELD } thing would probably not work, and if it did, would be weird since getting the reference is a runtime operation, unless we make some kind of macro to be able to get their references instead, which I am not so sure if its a good idea.

if we did allow:

let reference : &'static _ = &obj.CONST_FIELD;

it would be surprising that using a 'static lifetime for the reference of the const self field prevents its Drop implementation to be called.

@bjorn3
Copy link
Member

bjorn3 commented Nov 28, 2025

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?

@theemathas
Copy link

theemathas commented Nov 29, 2025

@RustyYato

Especially since we can specify that the reference behviour is the same as normal consts.

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.

@theemathas
Copy link

@bjorn3

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?

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.

@programmerjake
Copy link
Member

maybe a good option is to use ref instead of static:

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 unsafe):

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
}

@bjorn3
Copy link
Member

bjorn3 commented Nov 29, 2025

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.

That is semantically equivalent. The latter is merely an optimization over the former.

@RalfJung
Copy link
Member

RalfJung commented Nov 29, 2025

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.)

@RalfJung
Copy link
Member

An alternative to this would be that we make existing associated constants object-safe.

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 dyn Foo implements Foo. That would mean that <dyn Foo>::AGE should be valid. But that can’t really work, because the bare type dyn Foo doesn’t have any metadata / vtable attached. We only get that once we have an actual trait object. It cannot know the value of AGE because the context given: <dyn Foo>::AGE, does not involve an underlying type.

FWIW, there have been prior discussions about dropping the requirement that dyn Trait implements Trait. So this may still be a viable avenue. In terms of language feature economy, it seems nicer to reuse associated consts than to introduce an entirely new entity.

@izagawd
Copy link
Author

izagawd commented Nov 29, 2025

An alternative to this would be that we make existing associated constants object-safe.

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 dyn Foo implements Foo. That would mean that <dyn Foo>::AGE should be valid. But that can’t really work, because the bare type dyn Foo doesn’t have any metadata / vtable attached. We only get that once we have an actual trait object. It cannot know the value of AGE because the context given: <dyn Foo>::AGE, does not involve an underlying type.

FWIW, there have been prior discussions about dropping the requirement that dyn Trait implements Trait. So this may still be a viable avenue. In terms of language feature economy, it seems nicer to reuse associated consts than to introduce an entirely new entity.

@RalfJung

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 trait Foo { const AGE: i32; } example

if we have a collection of Vec<Box<dyn Foo>>, they would have to all have the same AGE value

with const self, they can have varying ages

@RalfJung
Copy link
Member

if we have a collection of Vec<Box<dyn Foo>>, they would have to all have the same AGE value

Why would that have to be the case?

@izagawd
Copy link
Author

izagawd commented Nov 29, 2025

if we have a collection of Vec<Box<dyn Foo>>, they would have to all have the same AGE value

Why would that have to be the case?

Oh nvm. I kind of imagined assoc consts with trait objects as Box<dyn Foo<AGE = 5>>. kind of like how it is done with assoc types.

But still, dropping the requirement that dyn Trait implements Trait, I can't imagine how that would work.

@izagawd
Copy link
Author

izagawd commented Nov 29, 2025

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.)

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;

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).

Copy link
Author

@izagawd izagawd Nov 29, 2025

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

Copy link
Member

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

Copy link

@theemathas theemathas Nov 29, 2025

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.

Copy link
Member

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 self in 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

T-lang Relevant to the language team, which will review and decide on the RFC.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants