In the first module here (no_opt), I can pass ctx to various helpers without any ceremony. In the second module (with_opt) I make that ctx optional. Now, it won't compile. I understand the error message, in a way, but I'm also confused by the difference here.
In the first case, I have a pointer. In the second, all I've done is say this pointer can also be null (0x0), so I feel like the code shouldn't have to change much. Is this because the first case is doing some transparent "reborrow" for me, which then stops with the addition of Option<...>?
How to say what I mean in with_opt with minimal syntactic clutter?
(playground)
struct Ctx { }
struct Stuff { a: A, b: B }
struct A {}
struct B {}
mod no_opt {
use super::*;
pub fn make_stuff(ctx: &mut Ctx) -> Stuff {
let a = make_a(ctx);
let b = make_b(ctx);
Stuff { a, b }
}
fn make_a(_ctx: &mut Ctx) -> A { A {} }
fn make_b(_ctx: &mut Ctx) -> B { B {} }
}
mod with_opt {
use super::*;
pub fn make_stuff(ctx: Option<&mut Ctx>) -> Stuff {
let a = make_a(ctx);
let b = make_b(ctx);
Stuff { a, b }
}
fn make_a(_ctx: Option<&mut Ctx>) -> A { A {} }
fn make_b(_ctx: Option<&mut Ctx>) -> B { B {} }
}
fn main() {
let mut ctx = Ctx {};
no_opt::make_stuff(&mut ctx);
with_opt::make_stuff(Some(&mut ctx));
println!("Done.");
}
Error:
error[E0382]: use of moved value: `ctx`
--> src/main.rs:23:24
|
21 | pub fn make_stuff(ctx: Option<&mut Ctx>) -> Stuff {
| --- move occurs because `ctx` has type `Option<&mut Ctx>`, which does not implement the `Copy` trait
22 | let a = make_a(ctx);
| --- value moved here
23 | let b = make_b(ctx);
| ^^^ value used here after move
|
note: consider changing this parameter type in function `make_a` to borrow instead if owning the value isn't necessary
--> src/main.rs:26:21
the plain &mut Ctx is implicitly reborrowed, but Option<&mut Ctx> is not, it is moved instead since &mut T is not Copy. you have to manually reborrow it. luckily, you can use as_deref_mut() for this:
pub fn make_stuff(mut ctx: Option<&mut Ctx>) -> Stuff {
let a = make_a(ctx.as_deref_mut());
let b = make_b(ctx);
Stuff { a, b }
}
You have an answer, but I'll elaborate some and provide a (syntactically noisier, but more powerful) alternative in case it's useful.
Yes, and to date only references have reborrowing, though there is an experiment to generalize it. Probably not landing any time soon though.
If Option<&mut T> had true reborrowing, you could implicitly create another copy of it that "disables" the original for some period... and this copy could remain valid even if the original Option drops. (I try to explain this ability in code form here.) When you apply reborrowing to a r: &mut T, you're reborrowing *r -- the T -- and you're not borrowing the r itself. And because r has a trivial destructor, it's okay if it goes out of scope.
In contrast, using Option::as_deref_mut creates a &mut Option<_> which does borrow the Option itself. So the return value cannot remain valid after the original Option drops. That's not important for your OP, but may come up elsewhere.
You may be able to still work around it by using patterns to drill down to the references...
fn foo<T>(s: Option<&mut T>) -> Option<&mut T> {
match s {
None => None,
// Implicitly reborrows instead of moving
Some(t) => Some(t),
// AKA:
// Some(&mut ref mut t) => Some(t),
}
}
...at the cost of less minimal syntactic clutter.
(If the reborrowing trait experiment is successful, this sort of transformation could happen implicitly, we hope.)
Thanks, both of you.
Unrelated to the re-borrowing, I may also be able to change my design here so my context parameter is &mut Ctx instead of Option<&mut Ctx>. In the real code, it's a Window in which I'm creating UI widgets, like Buttons, TextFields, and Stacks. I had been creating widget sub-trees and then adding them to a Window, but if I restrict creation to only happen with a Window, then code could be:
stack.add(ctx, button);
stack.add(ctx, label);
Instead of:
stack.add(ctx.as_deref_mut(), button);
stack.add(ctx.as_deref_mut(), label);
Maybe worth it.