Ocaml is a great programming language, I see it as a higher-level analogue to Rust. Because Rust was inspired by ML, a Rust-derived scripting language would certainly be similar to ML.
What I think makes Rust unique (compared to other ML languages) are impl traits (though Ocaml does support classes), borrow-checking, and zero-cost abstractions.
Lifetimes aren't the same as borrowing, though they are very similar. AFAIK, borrowing helps Rust infer the lifetimes of data in the program, and provides certain guarantees to how that data can be accessed and modified.
The Rust borrow checker, from what I understand, uses scoping and borrowing rules to determine where in the program variables are live, meaning still accessible. This region of where the variable is live is called the variable's lifetime. Rust currently uses the NLL borrow checker, which computes the lifetime of each reference, and the lifetimes of loans to that reference. A borrow checker error arises when a statement accesses a reference that violates some loan.
IIRC, The Polonius borrow checker intends to make this lifetime inference more flexible. Instead of directly computing the lifetime of everything, if starts by finding the origin of each reference. Polonius does away with directly computing liveness. Instead, it states that a loan is live if some live variable has that loan.
// modified from nikomatsakis presentation on polonius
let mut map: HashMap<u32, String> = HashMap::new();
let twenty_two = match map.get(&22) {
Some(v) => v,
None => { map.insert(22, "boop".to_string()); &map[&22] },
}
This would throw a borrow checker error with NLL, but not in Polonius, because v is not live in the None branch of the match.
But note that lifetimes aren't explicitly needed for borrowing to work. A system with borrowing (i.e. single mutable xor aliasable immutable) could manage lifetimes of the objects with a garbage collector, or statically determine the lifetimes using ASAP (as static as possible) memory management techniques.
I'm developing an experimental programming languages that forgoes garbage collection and other traditional memory management techniques. I hasn't been released yet as it's still under heavy development. In short, it tries to infer borrowing and lifetimes dynamically, using a memory-management-technique I call 'vaporization'. The rules of vaporization-based memory management are simple:
- Values are immutable, variables are mutable references to values.
- When a variable is reassigned or goes out of scope, the value it holds is released.
- When a variable is used, a copy of the data it contains is used.
It also makes the following optimizations:
- When a variable is passed to a function, a reference to its value is passed.
- The last use of a variable before it is released does not make a copy.
What does this look like in practice? Values are immutable, variables are references to values:
x = 7
y = x
x = x + 2
-- (comment) y is still 7
When a variable is reassigned or goes out of scope, the value it holds is released.
x = 7
x = 9
-- 7 is released
When a variable is used, a copy of the data is contains is used.
x = 7
y = x
-- y is not the same 7 as x
-- think of it as `let y = x.clone()`
Note, however, that although this system is memory safe, it's also memory intensive.
x = 17
y = x + x -- three copies of x exist
To combat this, a few optimizations are made. There are a few optimizations used, though I'll cover the most impactful ones. First, When a variable is passed to a function, a reference to its value is passed.
-- function syntax is `<pattern> -> <expression>`
increment = x -> x + 7
x = 7
y = increment x
Here, a reference to x is passed into increment. However, this language is fork on mut, so x wouldn't be copied until x + 7
inside increment. This prevents passing many copies of the same data around functions, say in a recursive function, for instance.
Finally, the last use of a variable before it is released does not make a copy.
x = 7
x = x + x
Let's annotate it. V<N> indicates that all V<N> are the same. Additionally, V<Nf> indicates the last use of V<N>.
x<1> = 7
x<2> = x<1> + x<1f>
Following the above rule, x<1f>
is not a copy of the value of x
, rather it is the value of x itself. When writing code that might mutate something in a linear manner, this significantly reduces the memory usage:
x = [1, 2, 3]
x = x + [4] -- no copies are made
This language also supports flexible hygienic macro system, which 'hides' the assignment in most cases (like mutating an object). In combination with passing references to functions, copies of the data are only made when the data needs to be used in two places at once. I haven't discovered any memory leaks or excessively high memory through testing yet. If you have any feedback, or notice that something's off, please leave a reply