Idea: Anonymous `impl` types that support capturing like closures

I don't understand why it is impossible to construct

#[anonymous_impl]
trait Person {
    fn name(&self) -> &str;
    fn set_name(&mut self, name: String);
    fn hello();
    fn pass_away(self);
}

And then call

life(Person::build_anonymous()
  .name(||&name)
  .set_name(|new_name| name = new_name)
  .hello(|| println!("Hello")),
  .pass_away(|| println!("Goodby"))
  .build()
)

Where the #[anonymous_impl] generates

struct <T1,T2,T3,T4> __AnonymousPerson 
  where T1: FnMut() -> &str,
             T2: FnMut(String),
             T3: FnMut(),
             T4: FnMut(),
{
  __namefn: T1,
  __set_name: T2
  ...
}

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).

The problem is that name is then captured by two separate closures, one of them mutably.

Thanks!

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.

2 Likes

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.

It actually works, you can use RefCell to fix the soundness issue and implement it entirely in Safe Rust: playground

Ah cool :slight_smile:

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?

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.