This RFC proposes per trait implementation fields that can be accessed through a value or trait object using new const self and const self ref syntax:
This allows code like:
When combined with traits, enables object-safe, per-implementation constant data that can be read through &dyn Trait in a more efficient manner than a dynamic function call, by storing the constant inline in the vtable, rather than as a method that returns it.
Motivation
Today, Rust has associated constants on types and traits:
For monomorphized code where Self is known, MyType::VALUE is an excellent fit.
However: You cannot directly read an associated const through a &dyn Foo. There is no stable, efficient way to write foo.VALUE where foo: &dyn Foo and have that dynamically dispatch to the concrete implementation’s const value.
The common workaround is a vtable method:
This forces a dynamic function call, which is very slow compared to the const self and const self ref equivalent, and does not have as much compiler optimization potential.
When using a trait object, const self and const self ref store the bits directly inside the vtable, so accessing it is around as performant as accessing a field from a struct, which is of course, much more performant than a dynamic function call.
Imagine a hot loop walking over thousands of &dyn Behavior objects each frame to read a tiny flag. As a virtual method, that's an indirect call per object — it mispredicts and blocks inlining. As a const self field it's a plain load from the vtable, which is much faster, and is much easier for the compiler to optimize.
Guide-level explanation
What is const self?
const self introduces per trait implementation fields, which are accessible through a self expression.
Example:
;
A const self field's type can have interior mutability, because the compiler does not operate on the field directly by its reference, even if it is stored in a trait object's vtable.
It first copies the field, and does the operations on that copied value, similar to how const variables work in rust.
This makes using it with interior mutability sound.
When using references like shown below:
let value : &u32 = &h.CONST_FIELD;
This works similarly to how const variables work in Rust: it copies CONST_FIELD, then takes a reference to that copy. Unlike a normal const item though, the resulting reference does not have a 'static lifetime; it has a temporary lifetime, as if you had written:
let tmp: u32 = h.CONST_FIELD; // copied
let value: &u32 = &tmp;
What is const self ref
const self ref is similar to const self, however, working on const self ref fields means working directly with its shared reference (no mut access).
The type of a const self ref field must not have any interior mutability to ensure soundness. In other words, the type of the field must implement Freeze. This is enforced by the compiler.
Example:
;
const self ref field's references have 'static lifetimes.
Note that unlike normal static variables, you cannot rely on the reference of a const self ref field to be the same reference of the same const self ref field of the same underlying type.
Trait objects and vtable fields
The main power shows up with traits and trait objects:
;
;
Accessing FORMAT_VERSION on a trait object is intended to be as cheap as reading a field from a struct: no virtual call, just a read from the vtable for that trait object.
It is much more efficient than having a format_version(&self), trait method, which does a virtual call.
On a non trait object, accessing FORMAT_VERSION will be as efficient as accessing a const value.
Naming conventions for const self and const self ref fields follow the same conventions as other const and static variables (e.g. SCREAMING_SNAKE_CASE as recommended by the Rust style guide); this RFC does not introduce any new naming rules.
To be more specific about which trait's const self/const self ref field should be accessed, a new instance.(some_path::Trait.NAME) syntax can be used.
T::FIELD would give a compile-time error when FIELD is a const self ref or const self field. These fields are only accessible through value syntax (expr.FIELD), not type paths.
How should programmers think about it?
Programmers can think of const self/const self ref fields as “const but per trait implementation” constants that can be read through references and trait objects, and a replacement for patterns like:
Where the data truly is constant and better modeled as a field in the vtable.
Teaching differences: new vs existing Rust programmers
For new Rust programmers, const self and const self ref can be introduced after associated constants:
- Types can have constants:
Type::CONST - Sometimes you want those constants visible through trait objects; that’s where
const selfcome in. - Sometimes you want to be able to directly reference those constants. Good for when it is too large; that's where
const self refcomes in. - You can access
self.CONST_FIELDeven if self is&dyn Trait, as long as the trait declares it.
Reference-level explanation
Restrictions
For const self FOO: T = ..;, we only ever operate on copies, so its type having interior mutability is fine.
For const self ref FOO: T = ..;, we get a &'static T directly from the vtable; to keep that sound we additionally require T: Freeze so that &T truly represents immutable data.
Both const self and const self ref field's type are required to be Sized, and must have a 'static lifetime .
Assume we have:
;
then it can be used like:
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. Reference lifetime is not 'static.
let variable3 = obj.Y; // ok if the type of 'Y' implements Copy
let variable4 : &'static _ = &obj.Y; // ok. Lifetime of reference is 'static, uses the reference directly
Resolution Semantics
For a path expression T::NAME where NAME is a const self or const self ref field of type T, it would give a compiler error.
This is because allowing T::NAME syntax would also mean that <dyn Trait>::NAME syntax should be valid, which shouldn't work, since the dyn Trait type does not have any information on the const value.
const self and const self ref fields are not simply type-level constants; they are values that can be efficiently accessed from a vtable.
For an expression expr.NAME where NAME is declared as const self NAME: Type or const self ref NAME: Type:
- First, the compiler tries to resolve
NAMEas a normal struct field on the type of expr. - If that fails, it tries to resolve
NAMEas aconst self/const self reffield from:- inherent impls of the receiver type
- If that fails, it then tries to resolve scoped traits implemented by the receiver type, using the same autoderef/autoref rules as method lookup.
- A struct cannot have a normal field and an inherent
const self/const self reffield with the same name. - If multiple traits, both implemented by type
Tand are in scope, provideconst selforconst self reffields with the same name andexpr.NAMEis used (whereexpris an instance of typeT), that is also an ambiguity error. The programmer must disambiguate usingexpr.(Trait.NAME).
Trait objects
For a trait object: &dyn Trait, where Trait defines:
We would have this VTable layout
[0] drop_in_place_fn_ptr
[1] size: usize
[2] align: usize
[3] do_something_fn_ptr
[4] AGE: i32 //stored inline
[5] LARGE_VALUE: LargeType //stored inline
This layout is conceptual; the precise placement of the contents in the vtable is left as an implementation detail, as long as the observable behavior (one vtable load per access) is preserved.
Lifetimes
Taking a reference to a const self ref field always yields a &'static T. This is sound since const self ref types are required to implement Freeze, are required to be 'static, and only provide a shared reference (you cannot get a mutable reference to it)
let p: &'static i32 = &bar.CONST_SELF_REF_FIELD;
However, you get a potentially different 'static reference every time you use the same const self ref field from the same type. This is because the storage for a const self ref field potentially lives in a trait object’s vtable, and different trait objects of the same underlying type do not necessarily share the same exact vtable.
Drawbacks
- Programmers must distinguish:
- Fields (expr.field),
- Associated consts (T::CONST),
- And const fields (expr.CONST_SELF_FIELD).
- Vtable layout grows to include inline
const selfdata, which:- Increases vtable size when heavily used.
- Needs careful specification for any future stable trait-object ABI.
- Dot syntax now covers both per-instance fields and
const selffields; tools and docs will need to present these clearly to avoid confusion.
Rationale and alternatives
Why this design?
-
Explicitly value-only access (expr.NAME) keeps the mental model simple, as it functions similarly to a field access
-
If you have a trait object, you can read its per-impl constant via the vtable
-
If you just have a type, associated consts remain the right tool.
-
By forbidding
T::NAME, we avoid:- Confusion over
dyn Trait::NAME. - Having to explain when a const is “type-level” vs “vtable-level” under the same syntax.
- Confusion over
-
A vtable load is cheaper and more predictable than a virtual method call. Especially important when touching many trait objects in tight loops.
Why not a macro/library?
A library or macro cannot extend the vtable layout or teach the optimizer that certain values are literals; it can only generate more methods or global lookup tables. const self/const self ref requires language and compiler support to achieve the desired ergonomics and performance.
Alternatives
Keep using methods:
; // remains the standard way.
Downsides:
- Conceptual mismatch (constant-as-method).
- Extra indirection and call overhead.
Prior art
As of the day this RFC was published, there is no mainstream language with a similar feature. The common workaround is having a virtual function return the literal, but that does not mean we should not strive for a more efficient method.
This RFC can be seen as:
- Making explicit a pattern Rust already uses internally: every
&dyn Traitvtable stores the size and alignment of the underlying type, read with a plain inline load rather than a virtual call.const selfgeneralizes that to user-defined values. - Exposing it in a controlled, ergonomic way for user code.
Unresolved questions
- Is there a better declaration syntax than
const self ref NAME : Type/const self NAME : Type? - Is
obj.CONST_SELF_FIELDsyntax too conflicting withobj.normal_field? - Is
obj.(Trait.CONST_SELF_FIELD)a good syntax for disambiguating?
Future possibilities
- Faster type matching than
dyn Any: Sincedyn Anydoes a virtual call to get theTypeId, usingconst self refto store theTypeIdwould be a much more efficient way to downcast. - The ability to read a
const self/const self refvalue from a vtable pointer alone, without an accompanying data pointer.