Files

119 lines
4.9 KiB
Plaintext

---
title: Class Internals
description: How classes are compiled under the hood — vtable layout, code generation, and safety verification.
icon: Shield
---
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:
1. A Rust struct with a `_super: Animal` field (or `_vptr: &'static [*const c_void]` for root classes)
2. A vtable constant with function pointers for each public method
3. An `impl` block with `Deref<Target = Animal>` and `DerefMut`
4. Method trampolines (`__m_<name>`) that are dispatched through the vtable
5. A `new()` constructor that initializes via `MaybeUninit` and calls the user's `constructor(&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:
1. **Direct assignment tracking** — Expressions like `self.field = value` are recognized as mutations of `field`. The analyzer checks for `=` and `->` operators whose left-hand side is a `self.field` path.
2. **`&mut self.field` tracking** — 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.
3. **Transitive method calls** — If the constructor calls `self.helper()`, the analyzer follows into `helper`'s body and tracks which fields *it* initializes. This transitively propagates through nested calls:
```mist
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
}
}
```
4. **Branch intersection** — For `if`/`else`, `match`, and loops, fields must be initialized in **all** branches. If one branch initializes `x` but another does not, `x` is considered uninitialized. This ensures soundness regardless of the runtime path:
```mist
pub constructor(bool flag)
{
if flag {
self.health = 100;
} else {
self.health = 0;
}
// Both branches init health ✓
}
```
5. **Super initialization** — When a class inherits, the `_super` field is added to the required-field list. Any assignment to `super` counts:
```mist
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 uninitialized
```
#### Override 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:
```rust
#[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:
1. **Before** the constructor body runs — enabling virtual dispatch inside the constructor itself
2. **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.