The Vale Programming Language

Owning Reference

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

vale
import stdlib.*;

struct Spaceship {
name str;
fuel int;
}

exported func main() {
ship = Spaceship("Serenity", 2);
// ship is an owning reference

println(ship.name);


// implicit (ship).drop()
}

References to Immutables

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.

vale
import stdlib.*;

struct Vec3 imm {
x int;
y int;
z int;
}
exported func main() {
firstRef = Vec3(3, 4, 5);
otherRef = &firstRef;
// Can use both freely.
println(firstRef.x + otherRef.y);

}
stdout
7

Moving

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.

vale
struct Spaceship {}

exported func main() {
a = Spaceship(); 2
b = a;
// Move the ship from a to b.
// b now owns the Spaceship.
// Can't use a now.
}

It works the same way when passing an owning reference to a function.

vale
import stdlib.*;

struct Spaceship { name str; }

func foo(b Spaceship) {
println(b.name);
}

exported func main() {
a = Spaceship("Raza");
foo(a);
// Move a into foo's b
// Can't use a now.
}

Side Notes
(interesting tangential thoughts)
0

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.

1

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.

2

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

Non-Owning References

Vale has two kinds of non-owning references:

  • The Borrow Reference, which asserts the target object is live when we make a borrow ref local variable (and then "tethers" the object so it's not freed).
  • The Weak Reference, which becomes null when we deallocate the struct it's pointing at.

These are explained in more detail below.

Note that there are no shared-ownership references 3 in Vale, except for immutable structs.

3

Like C++'s shared_ptr or Rust's Rc or all references in Java and Python.

Borrow References

To make a borrow reference, use the & symbol.

In this example, we're making a borrow reference to ship and giving it to the foo function.

vale
import stdlib.*;

struct Spaceship {
name str;
numWings int;
}
func foo(s &Spaceship) {
println(s.name);
}

exported func main() {
ship = Spaceship("Serenity", 2);
foo(&ship);

}

Weak References

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 borrow 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 borrow reference first.

In this example, we're making a o borrow reference from the ship.origin weak reference.

vale
import stdlib.*;

weakable struct Base {
name str;
}
struct Spaceship {
name str;
origin &&Base;
}
func printShipBase(ship &Spaceship) {
maybeOrigin = lock(ship.origin); 45
if (not maybeOrigin.isEmpty()) { 6
o = maybeOrigin.get();
println("Ship base: " + o.name);

} else {
println("Ship base unknown!");
}
}

exported func main() {
base = Base("Zion");
ship = Spaceship("Neb", &&base);

printShipBase(&ship);

(base).drop(); // Destroys base.

printShipBase(&ship);

}
stdout
Ship base: Zion
Ship base unknown!

4

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.

5

In future versions, 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.

6

In future versions, we could also use a match (match statement) here to check whether the ?&Spaceship contained anything.

Planned Features

These are planned features for Vale. See the roadmap for plans!

Pointers vs Values ("Inlining")

Inlining is used for optimization.

In these early days of Vale, objects are always on the heap. Soon, objects will by default be "inline", which means contained on the stack or inside their containing struct. Objects can be put on the heap with the ^ keyword.

This example puts the Spaceship on the heap.

vale
import stdlib.*;

struct Spaceship {
name str;
numWings int;
}
exported func main() {
ship = ^Spaceship("Enterprise", 4);
println(ship.name);

}
stdout
Enterprise

Next: Interfaces