Ownership
AEL, the Agent Engineering Language, will catch ownership mistakes before anything runs: using a value after it has moved, changing a value while something else reads it, or reading a value that was never set. Checked code will have no null values and no raw pointers.
Status
Planned for AEL Beta 0.0.1. AEL is not available yet.
Copy and move
Every value will have one owner: the binding, field or collection that holds it.
- Copied values: integers,
bool, unit and shared references, and arrays, structs, enums,OptionandResultmade only of such values. Passing one on will leave the original usable. - Moved values: bounded text, lists, exclusive references, and anything that holds one of them. Passing one to a function, assigning it, returning it, putting it into a struct or a message, or binding it in a
matchwill move it. The old place will not be usable until it receives a new value.
fn archive(note: Str<200>) -> () {
# archive owns note from here on
}
fn example() -> () {
let mut note: Str<200> = "first";
archive(note); # note moves into archive
# archive(note); # would fail the check: note has moved
note = "second"; # note has a value again
archive(note);
}
A struct will move as a whole: in the preview, you will not be able to move one field out and keep using the rest.
When a binding receives a new value, the new value will be computed first, and only then will the old one go. If computing the new value fails, the binding will keep the old one.
Borrowing
A reference will let code use a value without taking it.
| Reference | Created with | Allows |
|---|---|---|
Shared, &T | &value | Reading. Any number of shared references may exist at once. |
Exclusive, &mut T | &mut value on a let mut binding | Reading and writing. While it exists, it is the only way to reach the value. |
fn read(count: &i64) -> i64 {
return *count;
}
fn reset(count: &mut i64) -> () {
*count = 0;
}
fn example() -> i64 {
let mut count: i64 = 5;
let seen: i64 = read(&count); # shared, for the length of the call
reset(&mut count); # exclusive, for the length of the call
return seen + count;
}
*rwill read the value behind a reference. Writing through*rwill need an exclusive reference.- A shared and an exclusive reference to the same value, or two exclusive ones, will not be able to exist at the same time, including through a function call.
- You will not be able to move a value while a reference to it exists.
How long a borrow lasts
A borrow held in a binding will last until the end of the block that declares the binding, not merely until its last use. To end a borrow early, put it in a block of its own:
fn example() -> i64 {
let mut total: i64 = 1;
{
let view: &i64 = &total;
let seen: i64 = *view;
}
total = 2; # allowed: the borrow ended with its block
return total;
}
A borrow passed straight into a call will last for that call.
Where references will never go
In the preview, a reference will not be able to:
- be returned from a function;
- be kept in an agent's state that outlives the current call;
- be sent in a message to another agent or service;
- be held across a wait, such as a pending request or a timer.
These rules will refuse some programs that would in fact be correct. They will be deliberately strict, so that the check never has to guess.
Definite initialization
A binding declared without a value will have to receive one on every path before it is read.
fn choose(flag: bool) -> i64 {
let x: i64;
if flag {
x = 1;
} else {
x = 2;
}
return x;
}
Leave out the else branch and the check will refuse return x;. A value set only inside a while loop will not count after the loop, because the loop may run zero times. A binding without mut will be able to receive its value exactly once.
Ownership and messages
Sending a value to another agent will move it into the receiver's mailbox. If the mailbox is full or closed, the send will hand the value back to you, so it is never lost and never owned twice. Concurrency describes mailboxes.