Here is a basic Spaceship struct, with a couple members.
We can construct it using its constructor function, which has the same name and was automatically generated.
All structs are mutable by default, more on that in the Mutability section below.
Recall how we can use set x = 10; to modify a pre-existing variable x to 10.
We can use the set keyword with struct members too.
Here, we change the name member to "Raza".
We can specify a custom constructor for our struct.
We just need to give it the same name as the struct. 0
Inside the constructor, we must call either another constructor or constructor<T>.
When we create a mutable struct, we get the owning reference to it. When the owning reference disappears, the struct is automatically deallocated (via drop, explained below).
There are other kinds of references (constraint, borrow, weak), References explains more.
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 a struct.
Vale's ownership has the flexibility of C++'s unique_ptr without the mutability and aliasing restrictions of Rust and Cyclone, see References to learn how.
Every mutable struct has exactly one owning reference pointing to it.
We can make another reference to a struct with the & symbol. It will be a non-owning reference. 3 This is called a lend.
In this example, the type of owningRef is Spaceship, and the type of nonOwningRef is &Spaceship.
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.
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).
When an owning reference disappears, the struct is automatically deallocated. Vale does this by inserting a call to drop. 5
The drop function is automatically generated for each struct and interface.
Here we can see where the implicit call to drop is.
drop is called when the owning reference goes away, which in this case is ship.
If we specify #!DeriveStructDrop (which means "don't derive drop"), we can specify our own drop function instead. A custom drop could be used to:
Rule of thumb: if something must happen at some point in the future, put it in a drop function. Vale will make sure that it's not forgotten. 7
In other languages, drop has restrictions: it must take only a self parameter, and can't return anything.
In Vale, we have no such restrictions. drop can take parameters, can return anything, and doesn't even need to be name drop.
This example specifies #!DeriveStructDrop and instead defines a destroyShip function that takes a boolean parameter and returns an integer.
Since there is no drop function for the struct, Vale will never automatically destroy the object.
This is called Higher RAII, and it's a form of "linear typing" which we can use to enforce we call a certain function in the future.
See this article for more information on Higher RAII!
Drop functions also appear in C++ ("destructors") and Rust. Vale's drop functions are like those but more flexible: they can return values and even take extra parameters. In those cases, they must be called manually. See this article for more!
The destruct keyword is syntactic sugar for a "move destructure" pattern, see Pattern Matching for more.
This is an incredibly powerful pattern, see this article for more.
By default, structs are mutable. We can make immutable structs with the imm keyword.
After construction, an immutable struct cannot be changed at all.
Because of that, we can have multiple owning references to it, like Java or Python. 8
Vale also automatically derives the functions println, str, hash, ==, and more.
Immutable structs cannot have drop functions. 9
Small immutable structs (32b or less) are copied and passed by-value. Larger objects use SNRC (strategic nonatomic reference counting) to free themselves.
See this article for the reasoning behind this.
A tuple is a simple struct, whose members are named 0, 1, 2, etc.
We can make a tuple in Vale with parentheses (like (5, true, 42)), and can access them with a dot like arr.0.
These are planned features for Vale. See the roadmap for plans!
We normally call a function by name, such as the Spaceship("Serenity", 2) above. However, if the code is expecting a certain type, it can automatically call the constructor of the expected type.
This saves a lot of typing when calling functions.
We'll be able to produce a variant, which is something that can be one of two types (or more).
To make a variant, use an if-statement, and return a different type from the then branch than the else branch.
Here, a is a variant, either a string or an integer. a's type is (str|int).
Later, we can use the match statement to determine if a actually contains a str or an int.
We'll be able to access a arbitrary fields of tuples or structs with square brackets, like tup[1 + 1], which would produce a variant. 10
Specifically, indexing this tuple gives a variant (int|bool|str), with a + function that calls the appropriate actual + depending on the run-time type (int vs bool vs str).