4 ms·
They 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 everythin
by celeritascelery 3y ago
They 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