Vale has an ambitious goal: to be fast, memory safe, and most importantly, easy. There are a lot of stellar languages that have two, and we suspect it's possible to really maximize all three.
To do this, we're harnessing a new concept called regions.
In Part 1 we saw how we can use pure functions to easily immutably borrow data to make it faster to access.
Part 2 showed us how we could more precisely create regions via isolates, and immutably borrow them too.
The isolates we've seen so far have had complete isolation: no outside data can point to any inside data, and vice versa. This is useful for a lot of things, but there are times when we want inside data to point out.
Luckily, with one-way isolation, we can make data inside the region point to data outside the region.
Here's something similar to the example we saw in Part 2.
This snippet doesn't yet use isolation, we'll show that further below.
There are a couple differences from what we saw in part 2:
In Part 2, we were able to optimize this by making cannon isolated, because it didn't point to anything outside itself, and nothing outside pointed in.
However, Cannon now points to an EnergySource outside itself, so we can't use the isolation we saw in Part 2.
struct Cannon {
source &EnergySource;
strength int;
...
}
struct EnergySource {
energy int;
}
struct Ship {
hp int;
}
exported func main() {
source = EnergySource(200);
cannon = Cannon(&source, 12);
ship = Ship(100);
fire(&cannon, &ship);
println("Ship's new hp: {ship.hp}");
}
func fire(
cannon &Cannon,
ship &Ship)
void {
// Calculate damage using a very
// complex algorithm.
damage = cannon.strength * 2;
// Use up that much energy.
set cannon.source.energy -= damage;
// Now hit the ship!
set ship.hp -= damage;
}
Ship's new hp: 88
The answer is to use one-way isolation. Here's how!
Even though something inside the Cannon points outside itself, there's still nothing outside that points in.
In this case, we can still make cannon isolated, as long as we tell the compiler which parts of Cannon might point to outside itself.
We still make the changes from Part 2:
But there are a couple extra changes now, in Cannon and fire:
struct Cannon<e'> {
source &e'EnergySource;
strength int;
...
}
struct EnergySource {
energy int;
}
struct Ship {
hp int;
}
exported func main() {
source = EnergySource(200);
cannon = Cannon(&source, 12);
ship = Ship(100);
fire(cannon.imm, &ship);
println("Ship's new hp: {ship.hp}");
}
func fire<c'>(
cannon &c'Cannon<fire'>,
ship &Ship)
void {
// Very fast, no generation checks!
damage = cannon.strength;
// Take energy from energy source.
set cannon.source.energy -= damage;
// Now hit the ship!
set ship.hp -= damage;
}
As you can see, we're still able to isolate Cannon and open it immutably, even though it points to something outside its own isolated region.
A lot of real-world code can fit easily with one-way isolation. Most languages have a notion of "private" data, that's not exposed via an object's API. Those objects can be in the object's isolate, yet they can still point outside.
With one-way isolation, we can immutably borrow much more of our programs data a lot more often.
The best thing about one-way isolation is that it's opt-in:
This is consistent with Vale's philosophy of avoiding forced complexity.
This has two extra benefits to the programmer:
In a way, regions and isolation allow us to get the optimization power of borrow checking, with less restrictions and constraints.
As we saw, one-way isolation can allow us to use isolation even for data that points to things outside itself.
Part 4 shows how one object can contain another region's data inline, and Part 5 shows how that combined with one-way isolation can make certain patterns (iterating collections, calculating determinants, etc.) and entire architectures (like entity-component-system) zero-cost. 2
That's all for now! We hope you enjoyed this article. Stay tuned for the next article, which shows how one-way isolation works.
If you're impressed with our track record and believe in the direction we're heading, please consider sponsoring us on GitHub!
With your support, we can bring regions to programmers worldwide.
See you next time!
- Evan Ovadia
If anything isn't clear, feel free to reach out via discord, twitter, or the subreddit! We love answering questions, and it helps us know how to improve our explanations.
We're aiming to complete regions by early 2024, check out the roadmap for more details.
Together, isolates, pure functions, and one-way isolation combine to form something that looks suspiciously like an entire new programming paradigm... whether that's true remains to be seen!
This is still a draft! TODOs:
With your help, we can launch a language with speed, safety, flexibility, and ease of use.
We’re a very small team of passionate individuals, working on this on our own and not backed by any corporation.
If you want to support our work, please consider sponsoring us on GitHub!
Those who sponsor us also get extra benefits, including:
With enough sponsorship, we can:
We have a strong track record, and during this quest we've discovered and implemented a lot of completely new techniques:
These have been successfully prototyped. With your sponsorship we can polish them, integrate them, and bring these techniques into the mainstream. 5
Our next steps are focused on making Vale more user-friendly by:
We aim to combine and add to the benefits of our favorite languages:
We need your help to make this happen!
If you're impressed by our track record and believe in the direction we're heading, please consider sponsoring us:
If you have any questions, always feel free to reach out via email, twitter, discord, or the subreddit. Cheers!
Tentatively named the Vale Software Foundation.
Generational references, the linear-aliasing model, and higher RAII are all complete, and region borrowing, fearless FFI, and perfect replayability have been successfully prototyped. Be sure to check out the experimental version of the compiler!