Aliased Xor Mutable core for a high-level language
⚓ Rust 📅 2026-02-23 👤 surdeus 👁️ 2I have, I believe, discovered a way to use lifetimes and the Alias Xor Mutate rule to infer when references can be used and falling back to clones, without any borrow or lifetime annotations, which allows the creation of a high-level language with similar safety and performance guarantees as Rust.
fn main() {
let mut point = Point { x: 3, y: 4 };
point = double(point);
dbg!(point);
}
Transferring ownership back and forth looks like a high-level language, such as Python or Javascript, in the sense of not having any reference annotations. Ownership also means that there are no lifetime errors or annotations to deal with, at least when using mutable parameters. This is a good foundation for removing mutable borrows (&mut).
Ownership is purely conceptual, it is not something you can see in a disassembler.
parasyte (on Rust User forums)
Content about Rust might imply that moves must copy data to a new memory location, while mutable borrows don't; however, that's not truly the case. Sometimes that might occur, especially for smaller values, but being explicit about that is not a concern for a high-level programmer. It is good enough to have the language figure out when it can reuse memory. The following link explains how a move in Rust can compile to the equivalent of a mutable borrow.
Whether “move” operation on a stack value is cheap?
fn main() {
let mut point = Point { x: 2, y: 7 };
show(point);
point = double(point);
show(point);
}
fn show(p: Point) {
println!(" {}", p.y);
println!(" |");
println!("x +--{}", p.x);
println!(" y");
}
If mutable moves can be inferred as borrows, immutable moves can be also be inferred as borrows when passing immutable parameters, so that variables can be reused even after passing them to functions. Where the parameter is returned and the function is called at the end of the current scope, or lifetime, it can transfer ownership instead, as in the following example.
fn main() {
let outer = Point { .. };
let final = {
let inner = Point { .. };
further(inner, outer)
};
show(final);
}
// Further from (0, 0)
further(p1: Point, p2: Point) -> Point {
let origin = Point { x: 0, y: 0 };
let l1 = line(origin, p1);
let l2 = line(origin, p2);
if l1 > l2 { p1 } else { p2 }
}
With these two changes, users don't have to deal with lifetimes.
fn drop(f: move SpecialFile) {
// Code to close file.
}
A move annotation can be used for cases where you want to ensure a resource is closed and not reused. This can be inferred for parameters that are passed to functions that take ownership, such as built-ins and standard library functions.
After all that, people will still need to manage all their clones if we leave it there. While there's something to be said for keeping the costs explicit, the goal of a high-level language is to abstract over memory management. So, instead of calls to clone(), whenever a variable is re-used after a place that requires moving it, the variable will be cloned automatically.
fn moving_point() {
let p1 = Point..
// Move ownership because p1 isn't reused
let p2 = double(p1)
draw_circle(p2)
}
fn cloning_point() {
let p1 = Point..
// Clone to p2 because p1 is reused later
let p2 = double(p1)
draw_line(p1, p2)
}
There are, of course, some types which can't simply be cloned, such as files, so they'll need to be copied with explicit functions like copy_file(path). These are also the types that we might need to move for things like closing.
In summary, we default to borrows for all data, but if the data is aliased and mutated, then we clone it, otherwise it would violate the Alias Xor Mutate rule. We identify mutable borrows from mutation by assignment. Additionally, some types cannot be cloned, only borrowed or moved, which will be prevented if it violates the Alias Xor Mutate rule.
We can apply this idea to loops and closures as well.
pub fn main() {
let list = [
Point { x: 1, y: 1 },
Point { x: 9, y: 3 },
Point { x: 7, y: 6 },
];
for point in list {
point = double(point)
// Error:
// list = append(list, point)
};
dbg!(list)
// Point { x: 2, y: 2 }
// Point { x: 18, y: 6 }
// Point { x: 14, y: 12 }
}
When looping over a variable, only the individual items can be mutated, but not the variable itself, since that would be two mutable aliases.
pub fn main() {
let p1 = Point { };
// p1 is immutable, so aliasing is safe
let p1_print = || dbg!(p1);
p1_print();
let p2 = Point { };
// The closure mutates p2, but p2 isn't reused,
// so we can transfer ownership instead of cloning
let p2_print = || dbg!(double(p2));
p2_print();
let mut p3 = Point { };
// Clone p3 for closure because it's mutated later
let p3_print = || dbg!(p3);
p3 = double(p3);
p3_print();
dbg!(p3);
}
Similarly, a closure will use an alias if there is no shared mutable alias, otherwise it will use a copy.
There are a lot of interesting properties if this works, but I'm a hobbyist programmer without much experience, so even though I'm confident it will work, I don't have the skills or knowledge to verify it in a reasonable amount of time, so I've focussed on the core idea - the possibility of a high-level language with similar safety and performance as Rust. I await your questions and critique with great suspense.
2 posts - 2 participants
🏷️ Rust_feed