On Saturday, 22 October 2022 at 05:35:22 UTC, victoroak wrote:
> On Thursday, 20 October 2022 at 13:37:07 UTC, Don Allen wrote:
> fn main() {
let mut foo = 5;
let mut bar = || {
foo = 17;
};
let mut baz = || {
foo = 42;
};
bar();
println!("{}", &mut foo);
baz();
println!("{}", &mut foo);
}
I think the way it would be advised to write it in Rust would be something like this instead of using RefCell:
fn main() {
struct State {
foo: i32
}
impl State {
fn bar(&mut self) {
self.foo = 17;
}
fn baz(&mut self) {
self.foo = 42;
}
}
let mut state = State {
foo: 0
};
state.bar();
println!("{}", state.foo);
state.baz();
println!("{}", state.foo);
}
The problem is that both closures have a mutable reference to the same value and this is not allowed in Rust, you could solve this making the functions get a mutable reference to the variable but using a struct in this case is better IMO.
I think it's clear that my Scheme-ish attempt to use Rust closures was not a good idea. The fundamental problem with using Rust closures in this way is that the borrow-checker thinks any mutable borrows that occur in the closure happen at the time the closure is defined, not when it is called. This is one of those situations where trying to insure safety at compile time can be too conservative, which the Rust folks concede.
Interior mutability and reference counting are both run-time ways to get around the compiler when it prevents you from doing things that are actually safe but can't be proven safe at compile time.
I never actually fixed the code from which my example is drawn, which I wrote quite awhile ago, because at that point I'd reached my personal Rust-pain threshold and moved on to D, returning my blood pressure to normal. But if I were going to fix it, I agree with you that your (very nice) solution is preferable. Avoiding closures and instead using functions/methods results in the mutable borrows occurring at call time, which solves the problem, since my call pattern doesn't violate the "one mutable borrow at a time" rule. I'm sure Refcell would also work, as it did with my little example, but it's messier and introduces a bit of runtime overhead (which I doubt would matter in the application in question running on 4 GHz hardware, so I think the readability issue is primary).
I'd also add that in effect, what you are doing is manually creating closures by using a struct and struct methods (behind the scenes, Rust closures are built by the compiler with structs in much the same way). But by doing it manually, you get finer grained control over how and when the mutable borrows occur, so they happen in a smarter way than what the compiler is doing.