Class Internals
How classes are compiled under the hood — vtable layout, code generation, and safety verification.
This page covers the implementation details of Mist classes. You don't need to read this to use classes — it's here for curiosity and compiler contributors.
Under the Hood
A class Dog : Animal generates:
- A Rust struct with a
_super: Animalfield (or_vptr: &'static [*const c_void]for root classes) - A vtable constant with function pointers for each public method
- An
implblock withDeref<Target = Animal>andDerefMut - Method trampolines (
__m_<name>) that are dispatched through the vtable - A
new()constructor that initializes viaMaybeUninitand calls the user'sconstructor(&mut self)
The vtable is unified: parent entries are copied, overridden entries replace parent slots, and new methods are appended.
Safety
Classes use MaybeUninit::zeroed().assume_init() inside the generated new() function — an inherently unsafe operation. This raises a natural question: Are class constructors unsafe?
The answer is no. The compiler statically verifies that every field is initialized before the constructor returns, eliminating the undefined behavior that raw MaybeUninit would normally carry.
Static Field Initialization Verification
When a class has a constructor, the semantic checker (check_class_semantics) collects every declared field and walks the constructor body to prove each one is written to:
-
Direct assignment tracking — Expressions like
self.field = valueare recognized as mutations offield. The analyzer checks for=and->operators whose left-hand side is aself.fieldpath. -
&mut self.fieldtracking — Taking a mutable reference to a field (&mut self.field) also counts as initializing it, since the reference can only be taken if the field is being set up. -
Transitive method calls — If the constructor calls
self.helper(), the analyzer follows intohelper's body and tracks which fields it initializes. This transitively propagates through nested calls:
class Player
{
str& name;
i32 health;
pub constructor(str& name)
{
self.name = name;
self.setup_health();
}
void setup_health(&mut self)
{
self.health = 100; // Counts toward constructor's verification
}
}- Branch intersection — For
if/else,match, and loops, fields must be initialized in all branches. If one branch initializesxbut another does not,xis considered uninitialized. This ensures soundness regardless of the runtime path:
pub constructor(bool flag)
{
if flag {
self.health = 100;
} else {
self.health = 0;
}
// Both branches init health ✓
}- Super initialization — When a class inherits, the
_superfield is added to the required-field list. Any assignment tosupercounts:
pub constructor(str& name)
{
super = Super::new(name);
}If any field is uninitialized after the full analysis, a compile-time error is reported with the field's exact source location:
class field `Player.health` is uninitializedOverride Validation at Compile Time
When a method uses the override keyword, the codegen emits a hidden test function that verifies the Deref chain at compile time:
#[allow(invalid_value)]
fn __test_vt() {
let this: &Self = &unsafe { std::mem::MaybeUninit::<Self>::zeroed().assume_init() };
let _: &Target = this; // Forces compiler to check Deref<Target = Target>
}This ensures &Self can always deref into the base class type. If the inheritance hierarchy is invalid, the Rust compiler rejects it.
VTable Safety
The _vptr (vtable pointer) is set twice during construction:
- Before the constructor body runs — enabling virtual dispatch inside the constructor itself
- After the constructor body — in case a base-class constructor ran and overwrote the pointer
This ensures that virtual method calls work correctly even during object construction, without exposing uninitialized memory through the vtable.
Summary
| Risk | Mitigation |
|---|---|
Uninitialized fields via MaybeUninit | Static field-initialization verification rejects incomplete constructors |
| UB from reading uninitialized fields | Intersection analysis ensures all branches init the same fields |
| Invalid override signatures | Compile-time Deref test validates the inheritance chain |
| Vtable corruption during construction | _vptr is set before and after the constructor body |
| Unsafe code in generated constructors | #[allow(invalid_value)] is scoped to the generated new() only |
The MaybeUninit pattern is an implementation detail of the generated code — the Mist compiler proves soundness at the language level, so the user's constructor body is safe Mist code with no manual unsafe annotations required.