4 ms·
What Vale taught me about linear types, borrowing, and memory safety
- __stdcall__ 3y ago> If you've used a language like Rust before, this might feel familiar: own-lending is semantically equivalent to borrowing a &mut Random and passing it around. Is this a typo? I trust the author meant `Random` by move, or are they speaking to low level behavior where moves are elided to nothing where possible (and thus looks like a borrow)
- celeritascelery 3y agoThey are saying that these two are functionally equivalent: fn f(x: &mut T) and fn f(x: T) -> T If you wanted to avoid &mut you could pass everything “inout”. This pattern is sometimes used in Rust to avoid the limitations of mut references[1]. [1] https://doc.rust-lang.org/nomicon/lifetime-mismatch.html https://doc.rust-lang.org/nomicon/lifetime-mismatch.html
- tialaramex 3y agoAre they truly functionally equivalent? I agree the consequences are similar, and we'd often pick one over the other for implied semantics rather than what it necessarily does, but I wonder if there aren't cases where I can do one but not the other. In particular if all I have is a mutable reference, and no way to make a T surely there's no way to call that second function ? If I can make a T, I can make one (call it Geoff) and then core::mem::swap the reference with a mutable reference to Geoff, then call that "inout" function on Geoff (whose actual value is now the thing I had a mutable reference to) and swap them back afterwards. But if there's no way for me to get an actual T then I can't do that, how can I call it ?
- celeritascelery 3y ago> In particular if all I have is a mutable reference, and no way to make a T surely there's no way to call that second function ? That is true, but imagine that you replaced ALL “pass by mutable reference” with the “inout” pattern. That is essentially what vale is doing. And it functionally achieves the same thing (giving temporary ownership to a function).
- armchairhacker 3y agoThey’re not functionally equivalent for one reason: `panic` unwinding. Rust can catch panics, and if you catch a panic while calling the latter, you will effectively drop `T`. Otherwise, if you have a temporary value (e.g. `Default`), you can convert the former to the latter via `std::mem::replace` or `std::mem::take` (which replaces with default) fn f1(x: &mut T) { let y = std::mem::take(x); *x = f2(y); } and you can always convert the latter to the former fn f2(mut x: T) -> T { f1(&mut x); return x; } See also the `replace_with` crate: https://docs.rs/replace_with https://docs.rs/replace_with
- tialaramex 3y agoThis seems like it's just re-stating what I said? I guess it can be tidier to use replace rather than swap depending on the circumstances. Edited-to-add: The replace_with crate seems like it's taking the opposite approach to Aria's "Pre-pooping your pants" essay and I don't like that. It has a bunch of safety pre-requisites on functions labelled safe, which is not good Rust.
- xavxav 3y agoThey aren’t unless you’re willing to go significantly more complex than this naive translation suggests. As an example, consider the index_mut function with signature fn index_mut(&mut Vec<T>, ix: usize) -> &mut T How do you represent this? However, this insight holds for relatively common forms of ownership, and you can see this exploited in electrolysis: https://github.com/Kha/electrolysis https://github.com/Kha/electrolysis
- cultofmetatron 3y agocan't believe he managed to get though this entire article without once mentioning the artifact by name. its called the rosetta stone and its currently held in the ancient history museum of London. definitely worth seeing in person.
- levzettelin 3y ago*British Museum
- samsquire 3y agoThis is really interesting verdagon, thank you. I wrote a multithreaded reference passing implementation that runs at runtime. If you have the reference you can use it, the condition is that you have to give it back when you're finished with it.
- deleted 3y ago[deleted]
- epage 3y agoI feel like I'll have to read up some more to have the background to fully understand the article but one thing I wanted to point out is a difference between `fn rand_int(rand: &mut Random) -> i64` and `fn rand_int(ran: Random) -> (i64, Random)`, at least in Rust. With the version that moves `Random`, the return type is bigger and may spill over from registers to the stack which may cause a significant slow down, more than the code of Vale's generation check, I suspect. I've run into this with my parser combinator library, winnow [0], and am considering switching from the nicer functional model of moving to the more imperative model of `&mut` [1]. [0]: https://docs.rs/winnow/latest/winnow/ https://docs.rs/winnow/latest/winnow/ [1]: https://github.com/winnow-rs/winnow/issues/72 https://github.com/winnow-rs/winnow/issues/72
- mike_hock 3y agoSpeaking more generally, to me this move-in-move-out interface design is a non-starter, so programming languages and compilers will have to come up with other solutions to avoid unnecessary checks. Which, AFAIK, Rust already has (there's runtime check on `&mut Random`), so TFA really is a Vale-specific micro-optimization.