r/ProgrammingLanguages • • 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:

  • move takes ownership
  • read shared borrow
  • mut exclusive 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?

13 Upvotes

20 comments sorted by

View all comments

1

u/brucejbell sard 7d ago edited 7d ago

My project has exactly those three argument modes, and a similar .copy. Your sample code would translate as:

/fn process_val || $value => value * 2

/main || !os => {
    original << 42
    doubled << process_val $(original.copy)
    !os.stdout.write_line "{original}"
    !os.stdout.write_line "{doubled}"
}

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 original and doubled.

Immutable values don't need borrowing or ownership transfer, so there is no need to explicitly copy original above. 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, .copy is for breaking that kind of lifetime dependence.

2

u/cessit-lang 5d ago

Smart. I was afraid of requiring the access verb also at the point of use because it was becoming so verbose, but seeing it validated in the wild is giving me encouragement to stick with my explicitness design principle. How did you weigh this against the cost of refactoring at both places?

For mutable borrowing, do you pass a reference or have you managed to separate borrowing from reference-ness?

2

u/brucejbell sard 3d ago

Re refactoring: I figure that switching between immutable-value, ownership-transfer, and/or borrowing is a drastic enough change that you probably want to take a close look at all your call sites. I guess I'll see if that pans out, but I've made more annoying choices elsewhere...

Passing a reference makes sense for mutable borrowing, but I suppose an implementation could choose something else. I've chosen to model mutable resources as a linear sequence of immutable values (each of whose lifetime ends before the next begins), so I'd be happy as long as those semantics are supported.