An owning reference keeps an object alive. When a struct has no owning reference pointing to it anymore, the struct is deallocated.
Every mutable struct has exactly one owning reference pointing to it. 0
Immutables, such as primitives or immutable structs, can have multiple owning references pointing to them. 1
When we say a = b; for an immutable struct, both a and b can still be used.
Immutables are simple to use. The rest of the page is about references to mutable structs.
A local can give up the owning reference. Afterwards, that local is gone, and we cannot use it. This is called a move.
This is used to influence when the struct is dropped, to keep it alive for longer or destroy it sooner.
In this example, the a local is giving up the owning reference, and we're putting it into the b local.
It works the same way when passing an owning reference to a function.
Ownership is also found in C++ (unique_ptr), Rust, and Cyclone.
C also has "conceptual" ownership, in that we must track ownership without the language's help, to know when to free an object.
Vale's references has the flexibility of C++'s unique_ptr while guaranteeing memory safety.
These are references from the user's perspective, but under the hood, references to small immutables (including primitives and small structs) are inlined and copied around, for performance.
Rather than thinking "a is an owning reference", think instead that a is a local that currently contains an owning reference.
On the next line, a is destroyed, but the owning reference that it contained still lives on (inside b).
Vale has two kinds of non-owning references:
These are explained in more detail below.
Note that there are no shared-ownership references 4 in Vale, except for immutable structs.
Specifically, in assist mode, see below.
Like C++'s shared_ptr or Rust's Rc or all references in Java and Python.
We can use constraint references to detect when we accidentally destroy an object we're still pointing at. 5
This behavior helps us find logic errors. 6 7
To make a constraint reference, use the & symbol.
In this example, we're making a constraint reference to ship and giving it to the foo function.
For example, if we have an open UI dialog for editing an entry in a given spreadsheet, the dialog would want a constraint reference to the entry. 8
If we delete an object while a constraint reference is pointing to it, the program halts and shows which constraint reference was violated. 9
In this example, Vale detects that shipCRef becomes a dangling 10 pointer and halts the program so the developer can fix it.
To avoid the program halting:
A constraint reference is similar to a foreign key constraint in SQL: it prevents us from deallocating the object it points to.
This behavior is turned off in production, see resilient mode below.
This behavior is not a memory safety feature. Vale's memory safety comes from resilient mode's hybrid-generational memory.
A constraint reference is the best kind of reference to use here. A weak reference just delays the halt until we try to dereference it. It's often better to catch bugs sooner.
Planned feature; see Roadmap!
Dangling means pointing to a deallocated struct.
For example, by copying the needed data out. In this example, we could have copied into a wings local.
We can also configure Vale to have different behavior for constraint references.
Different modes will be useful at different times.
In the last example, the program would instead have crashed on the final println, because that's when the shipCRef constraint ref is used.
This is safer than C++, because we can do our testing in assist mode, giving us more confidence that we aren't even making any pointers dangle, which makes us even more confident that we wouldn't accidentally dereference any dangling pointers.
We often have a reference to a struct, and we would be fine with that struct being deallocated while we have a reference to it.
In these situations, we use weak references.
A weak reference will set itself to null when the object it's pointing at is destroyed.
We can make a weak reference with &&.
To use a weak reference, call lock, which will return an optional constraint reference (?&Spaceship). We can then check if it contains a &Spaceship or is empty.
In other words, we cannot reach into a struct through a weak reference directly; we must lock it to try to make it into a constraint reference first.
In this example, we're making a o constraint reference from the ship.origin weak reference.
Locking a weak reference gives us a ?&Spaceship, also known as an Opt<&Spaceship>. This has two possible subclasses: None<&Spaceship> which is empty and Some<&Spaceship> which contains a &Spaceship.
These three lines can be reduced to if ((o) = lock(ship.origin)) {.
This would specifically check if that ?&Spaceship is the one-member subclass (Some<&Spaceship>) and if so, assigns its member to o. This is a conditional destructure, see Pattern Matching for more.
We could also use a mat (match statement) here to check whether the ?&Spaceship contained anything.
These are planned features for Vale. See the roadmap for plans!
Inlining is used for optimization.
By default, a reference is a pointer, to something on the heap. 17 A reference can instead be a value on the stack ("inline"), by using the inl keyword.
Inline objects are much faster to allocate and deallocate, but cannot be moved.
inl is most often used in structs. 18
When we use inl in a struct, the member's memory is here inside the containing struct's memory.
In this example, the Engine's memory is inside the Spaceship's memory. This makes Spaceship bigger, and avoids allocation/deallocation overhead.
In a way, inl means "It should live here, not somewhere else."
Vale uses escape analysis to automatically inline every local that doesn't get moved into a global or parameter. The inl keyword is useful to force it, when optimizing.
Vale can even inline interfaces if they're sealed, resulting in something similar to tagged unions in C, or enums in Rust. See Interfaces for more.
Next: Interfaces