I think this would work unless you want the anonymous impl to outlive the calling scope and you need to share data between functions (otherwise move will fix it).
I think it's possible to do this with macros. If you have the following code:
fn main() {
let mut x = true;
let y = anon_impl! {
impl Thing {
fn statik() -> u32 {
123
}
fn borrow(&_, arg: f32) -> String {
format!("{}{}", arg, x);
}
fn borrow_mut(&mut _) {
x = false;
}
}
};
}
Then this could expand to:
fn main() {
let mut x = true;
let y = {
enum Dispatch {
Borrow(f32),
BorrowMut(),
}
union Return {
borrow: std::mem::ManuallyDrop<String>,
borrow_mut: std::mem::ManuallyDrop<()>,
}
struct Anon<F> {
func: std::cell::UnsafeCell<F>,
}
impl<F> Thing for Anon<F>
where
F: FnMut(Dispatch) -> Return,
{
fn statik() -> u32 {
123
}
fn borrow(&self, arg: f32) -> String {
let func = unsafe { &mut *self.func.get() };
let ret = func(Dispatch::Borrow(arg));
std::mem::ManuallyDrop::into_inner(unsafe { ret.borrow })
}
fn borrow_mut(&mut self) {
let ret = self.func.get_mut()(Dispatch::BorrowMut());
std::mem::ManuallyDrop::into_inner(unsafe { ret.borrow_mut })
}
}
Anon {
func: std::cell::UnsafeCell::new(#[inline(always)] |dispatch: Dispatch| {
match dispatch {
Dispatch::Borrow(arg) => Return { borrow: std::mem::ManuallyDrop::new({
fn force_fn<F: Fn() -> String>(_func: &F) {}
let func_ref = #[inline(always)] || {
format!("{}{}", arg, x)
};
force_fn(&func_ref);
func_ref()
})},
Dispatch::BorrowMut() => Return { borrow_mut: std::mem::ManuallyDrop::new({
x = true;
})},
}
}),
}
};
}
Does this work? (It might be broken/unsound. I haven't thought about it too deeply.)
Edit: Actually it is unsound because you get multiple mutable references to the inner closure, which is UB even though it doesn't use them like mutable references. There might be a way to fix this though.
In this case the x = false might allow you to infer that x's type is bool, but what if it wasn't there? How do you find the name without weird type_alias_impl_trait tricks? Moreover, you also need to distinguish identifiers of functions/other global items from those of local variables you want to capture. And finally, how do you determine how you should capture local variables (by move, by shared borrow or by exclusive borrow)?
The macro expands to code which contains a closure which contains all the user-provided code. You don't need to worry about detecting types (the expanded code doesn't contain the word bool anywhere) or knowing what kind of thing a name refers to because the closure will figure all that out automagically.
To move local variables rather than borrow them the macro could just allow an optional move keyword before impl which it would put in front of the closure.
I was aiming to make it zero-cost with the UnsafeCell, but being sound is certainly more important (the cost of the RefCell would be trivial anyway, I expect). Though it does make it !Sync.
I wonder if there's some tricky way of supporting both by-move and by-ref methods simultaneously?