My current project has a GUI, and therefore uses callbacks. The same object in memory can be changed by several different GUI elements, and therefore needs to be accessed by several callbacks.
I find myself wanting to use this kind of pattern:
Of course, it does not work because “closure may outlive the current function, but it borrows context”. That is easily fixed by making the closures move. That works for one callback. But as soon as there is a second one, “capture of moved value: context”.
This is solved by allowing several references to context, using Rc for example:
let mut gui = GUI::new();
let context = Rc::new(Some_struct::new());
let context2 = context.clone();
let context3 = context.clone();
Now I can use context2 in the first callback and context3 in the second callback. It works!
But it is annoying. Is there a simpler solution?
If not: do you think it could warrant a little compiler magic?
It could be a trait, something like ClosureKeep: if a closure needs to move/borrow a variable xand if that move/borrow is not possible due to lifetime and/or later uses and ifx implements the ClosureKeep trait, then use x.ClosureKeep::clone() to get a second copy of x` that the closure can keep.
let context = Rc::new(Some_struct::new());
// assuming `add` takes &self
gui.register_callback("button_more", clone!(context; || { context.add(1); }));
// if we don't need `context` in this scope any more, just move it
gui.register_callback("button_less", move || { context.add(-1); });
Clones are often heavy and can cause significant slowdown if done silently.
Besides, this solution is too specific to your use case. In your use case you were okay with sharing Context; for some other people this may not be the case. Compiler magic would make it easier for your use case but might break other use cases.
I know, and that is precisely the reason I suggested a specific trait rather than relying on Clone directly.
Possibly, that is the reason I asked the question on the forum rather than opening a feature-request issue. But my experience with different kind of projects lets me suspect it may be rather frequent.
More generally, I suspect there is room for a trait, or more probably a marker, between Copy and Clone, to indicate types where cloning is not trivial (otherwise, it would be Copy) but still lightweight enough to be done automatically.
Now that I think of it, it has nothing to do with closure specifically, it applies to any case where the API requires a move and the value is still needed. Consider this, for example:
// base : String
let s1 = base + "suffix1";
let s2 = base + "suffix2";
It does not work because Add moves its arguments and String is not Copy. It can be fixed with .clone() for the first call, but it is very inelegant, if only by breaking the symmetry.
If the compiler knew of a AutoClone marker and String had it, it could insert the clone call automatically if and only if needed.
And of course, it would be the responsibility of type implementations to only implement AutoClone when it is safe. A warning, #[warn(auto_clone)], could also be enabled for developers who do not want to rely on this feature.
(By the way, is there somewhere a comprehensive list of warnings?)
It's not just a matter of safety, right? For performance or memory reasons, perhaps I want to be very careful about when/where Strings get allocated. In this case, I don't want String to be AutoClone. Does your proposal allow for one project to request that String be AutoClone and another project to not use AutoClone at all?
It works because you can always rework your code to avoid using the feature: adding the explicit call to clone() would always work, of course, and depending on the code, other more efficient solutions may be possible, like changing the order of the operations.
Note that this is pretty nontrivial to do right; I'm considering constructing a CFG (or using the existing one if possible) and using dataflow equations or something.
Though it may be possible to use the existing ExprUseVisitor or some other functionality from borrowck to get this. I haven't thought about it.
Yeah, I was just looking at some --unpretty flowgraph output to make some sense of the CFG stuff. I'm looking forward to your thoughts how to best tackle this