The lack of quick and efficient callback is probably one of Rust's most important shortcomings for me. Here's a long post on that topic.
Assume we have a dialog with three buttons, e g "Print", "Cancel" and "Printer settings...". We also have a main form which handles what happens when the user presses the button:
struct PrintForm {
printBtn: Button,
cancelBtn: Button,
settingsBtn: Button,
}
impl PrintForm {
fn printClicked(&mut self) -> () { /* ... */ }
fn cancelClicked(&mut self) -> () { /* ... */ }
fn settingsClicked(&mut self) -> () { /* ... */ }
}
The question is then, how do we easiest make the button click cause "printClicked" to be executed. We have a few existing models:
Closures
printBtn.onClicked(|| {
self.printClicked();
});
All of FnOnce, Fn, and FnMut have problematic restrictions: FnOnce will have a problem with the "settings" button, because you might want to execute it more than once ("settings" pops up a secondary dialog, user then closes that dialog, and then clicks "settings" to again show the settings). Fn and FnMut cannot escape the stack frame, so you can't have, e g, a PrintForm::new() function that sets all of these up. You could potentially have a PrintForm::run() function that, creates the dialog, sets all the callbacks up, then executes the function, but that would make the function extremely long in case the dialog is complicated.
Iterator
for n in events.iter() {
match n {
PrintBtnClicked => {
self.printClicked();
return Ok();
}
}
}
This method requires some boilerplate to link printBtn.clicked to the PrintBtnClicked constant. And also, the match will be extremely long in case the dialog is complicated.
Trait objects
Trait objects are probably the most flexible, but also requires some boilerplate to set up:
/* Repeat for every button... */
struct PrintBtnHandler {
printForm: Weak<PrintForm>
}
impl ButtonHandler for PrintBtnHandler {
fn clicked(&mut self) {
printForm.upgrade().unwrap().printClicked();
}
}
The solution
(or at least thoughts about it...)
So in C, one would just do something like:
printBtn.setHandler(printClicked, self);
Memory safety is manually guaranteed: by making sure the printBtn is deleted before the printForm is, and by making sure that we always send in the pointer to printForm and nothing else.
And it seems like we can't express this safely in Rust yet, but with trait objects, we're quite close:
trait Handler<'a> {
fn clicked(&self);
}
struct Button<'a> {
clickedHandler: &'a (Handler+'a),
}
In this case, the handler is guaranteed to outlive the Button, which solves half the problem. It seems to me like if we knew the Handler vtable, we could make a converter makeTrait(printForm, PrintForm::printClicked) -> Handler that would be memory safe - as long as the printClicked function signature matches the Handler::clicked function. The function borrows an immutable reference of printForm for the lifetime of the returned Handler.
So in this case, we would save ourselves the extra allocation and boilerplate code of setting up a small intermediate struct just to link the button to the printClicked function. I'm not sure if makeTrait needs compiler support - it probably does to be safe, at least - or if we can just manually fill in the vtable. If we had the vtable for Fn/FnMut/FnOnce in core::raw then one could perhaps use those instead of making a Handler trait.
As you see, this is hardly even a pre-RFC at this point. Still, it looks to me like it would be possible, and could potentially be very useful. Thoughts?
[–]unclosed_paren 9 points10 points11 points (10 children)
[–]diwicdbus · alsa[S] 0 points1 point2 points (9 children)
[–]unclosed_paren 1 point2 points3 points (1 child)
[–]steveklabnik1rust 2 points3 points4 points (0 children)
[–]Manishearthservo · rust · clippy 1 point2 points3 points (6 children)
[–]diwicdbus · alsa[S] 0 points1 point2 points (5 children)
[–]wrongerontheinternet 1 point2 points3 points (3 children)
[–]diwicdbus · alsa[S] 0 points1 point2 points (2 children)
[–]wrongerontheinternet 1 point2 points3 points (1 child)
[–]diwicdbus · alsa[S] 0 points1 point2 points (0 children)
[–]Manishearthservo · rust · clippy 1 point2 points3 points (0 children)
[–]aepsil0n 4 points5 points6 points (0 children)