Remix.run Logo
Panzerschrek 2 days ago

> Borrow Checking

It's very confusing name for this feature. It suggest that some sort of borrowing takes place and that it's just an optional check, which isn't the case. It should be named something like "enforced static usage analysis" instead.

In my programming language I have similar mechanism. But it isn't just checking, since it affects code generation by tracking which variables are still in use and which can be destroyed.

zahlman a day ago | parent | next [-]

> It suggest that some sort of borrowing takes place

It does. A value is passed by reference, borrowing it from the owner.

> and that it's just an optional check

I don't see how the word "check" implies that it's optional.

rcxdude 2 days ago | parent | prev | next [-]

In rust it's a lot closer to optional: you can in principle compile rust without doing any borrow checking at all (and I believe in practice mrustc does not bother to implement it, because it's primarily used for bootstrapping and so assumes it is already being passed code that compiles with regular rustc).

dnautics 2 days ago | parent | prev | next [-]

You can very likely borrow check in languages that don't have it in the type system. Exactly the way you suggest, as an optional add-in. It's still WIP but in my side project I haven't found cases that can't be handled yet.

https://github.com/ityonemo/clr

Panzerschrek 2 days ago | parent [-]

> Exactly the way you suggest, as an optional add-in

No, I don't suggest it, but criticize it. Rust performs its checking as a separate step after actual compilation, which sometimes leads to strange behavior (like borrow errors are shown only after actual compilation errors). I prefer an approach which is integrated with other language mechanisms.

> It's still WIP but in my side project I haven't found cases that can't be handled yet.

It's generally a good idea to write such an analyzer, but I doubt it can be useful without proper integration with the language itself (with huge semantics changes). If it's too strict, it will reject perfectly fine code, but otherwise it will catch only the most obvious errors and approve code having more complex memory bugs.

jkhdigital 2 days ago | parent | next [-]

I think Pony’s reference capabilities are a better solution for linear types. It’s just part of the language so a violation is simply a type error, not something flagged later during static analysis.

dnautics a day ago | parent | prev [-]

> but I doubt it can be useful without proper integration with the language itself

So projects like mypy or sorbet aren't useful because they aren't properly integrated?

jeltz 2 days ago | parent | prev [-]

In Rust variables are not destroyed after the last borrow ends but instead when it goes out of scope. I guess that is why it us called borrow checking.

Panzerschrek 2 days ago | parent [-]

> In Rust variables are not destroyed after the last borrow ends but instead when it goes out of scope

That's the problem. Once I had a tricky case, where I locked a mutex in a match expression only to read a single field to match from the mutex contents. In one of branches of the match expression I locked this mutex once again and got a deadlock. Rust compiler wasn't smart enough to realize that the temporary variable for the mutex lock object should be destroyed earlier (it's no longer needed). So, I needed manually reading the field I need into a named variable to eliminate this deadlock.

A more advanced temporaries lifetime analysis would solve problems like described above, but it means basically duplicating a lot of stuff which is already done in the borrow checker (which runs as an afterpass).

asQuirreL 2 days ago | parent [-]

Rust already supports the kind of behaviour you are describing for borrows, because of non-lexical lifetimes. Code like the following now compiles:

    fn main() {
      let mut x = 42;
      let y = &x;
      println!("{y}");
      let z = &mut x;
    }
Even though y's scope overlaps with z's, and they introduce conflicting borrows, this code compiles because the compiler treats y's borrow as dead after its last use (this has been true since Rust Edition 2018, so for quite some time now). If you move the println after the mutable borrow then it fails to compile.

However values whose types have Drop are another matter. They are treated as if there's an explicit call to their drop function at the end of their lexical scope which pins their lifetime. This is intentional and desirable precisely because of the guard pattern (like for mutexes).

If you didn't have that guarantee, at worst your mutex's guard object would be immediately dropped because it's never referenced after it's created, or at best it would be very tricky to understand what the protected critical region is.

Panzerschrek 2 days ago | parent [-]

> If you didn't have that guarantee, at worst your mutex's guard object would be immediately dropped

For named local variables it's a different story. They should remain alive until the end of their lexical scope. But for unnamed temporaries created in expressions different rules should apply - as soon as there is no reference to such temporary, it should be destroyed.

asQuirreL a day ago | parent [-]

Okay, I see. The issue you are running into is specifically mentioned in this article about how Rust currently does lifetime extension:

https://smallcultfollowing.com/babysteps/blog/2023/03/15/tem...

Typically, a temporary's lifetime is bounded by the statement it is in, but for the subject of a match, this extension overlaps with all its arms even if the temporary borrow is not used after the subject is evaluated (i.e. you borrowed, you read and copied a field out of the borrow).

The issue seems to be that this is a syntactic transformation, but the expected behaviour requires type information, so you can tell whether to extend the temporary's lifetime by whether that lifetime leaks the immediately containing scope.

This is kind of similar to how type parameter unification in Hindley-Milner works. There's even an analogy made between the two things here:

https://okmij.org/ftp/ML/generalization.html#gen-mismanageme...