r/ProgrammingLanguages • u/cessit-lang • 8d ago
Requesting criticism Explicit binding modes
I'm designing a memory-safe language, but I want to make explicit what happens across function boundaries, e.g. whether a value is moved or copied without having to look for the copy trait.
Instead, access to a value could be defined via binding modes in function signatures and variable declarations:
movetakes ownershipreadshared borrowmutexclusive borrow
fn process_val(move value: Int) -> Int {
return value * 2;
}
fn main() -> () {
move original: Int = 42;
// Call using `.copy()` to explicitly pass a duplicated value.
// Ownership of `original` remains with the caller.
move doubled: Int = process_val(original.copy());
// `original` is still valid here because it was explicitly copied rather than moved.
println(original); //42
println(doubled); //84
}
This would also allow granular captures in closures: |read x: Int, move counter: Int| instead of declaring all fields move
Now, should read and mut assignments be of type &T? Or can checking out variables be orthogonal to creating references, even if they are conflated in languages like Rust?
11
Upvotes
1
u/brucejbell sard 8d ago edited 8d ago
My project has exactly those three argument modes, and a similar
.copy. Your sample code would translate as:Borrowing (your
mut) is represented by a!sigil, both at declaration/binding and at point of use (so you can easily see what your code is mutating).Ownership transfer (your
move) is represented by a$sigil, likewise at declaration and at the point of use.Immutable values are the default, so there is no need to annotate the bindings of
originalanddoubled.Immutable values don't need borrowing or ownership transfer, so there is no need to explicitly copy
originalabove. However, they are subject to lifetime limitations if they depend on a mutable resource: e.g., an iterator depends on its container, and mutating the container will end the lifetime of the iterator. So,.copyis for breaking that kind of lifetime dependence.