Better handling of Fn(&mut T) and friends for improved Futures

This code works:

fn main() {
    fn z<'a, 'b>(x: &'a mut std::borrow::Cow<'b, str>) { println!("{}", x); }
    let x: &(for<'a> Fn(&'a mut std::borrow::Cow<str>) + 'static) = &z;
    let y: &std::any::Any = &x as &std::any::Any;
    (y.downcast_ref::<&(for<'a> Fn(&'a mut std::borrow::Cow<str>) + 'static)>().unwrap())(&mut std::borrow::Cow::Borrowed(&*format!("{}", "Foo")));
}

This (supposedly) equivalent code doesn’t:

// https://play.rust-lang.org/?version=stable&mode=debug&edition=2015&gist=cea5893bbeac1f4e5fad0081d1288d08
use std::{sync::Arc, borrow::Cow, any::Any};

struct Handler<T> {
    f: Arc<dyn for<'a> Fn(&'a mut T) + 'static>,
}

fn make_handler<T, F: for<'a> Fn(&'a mut T) + 'static>(f: F) -> Handler<T> {
    Handler { f: Arc::new(f) }
}

fn main() {
    fn z<'a, 'b>(x: &'a mut Cow<'b, str>) { println!("{}", x); }
    let x: Handler<Cow<str>> = make_handler(z);
    let y: &Any = &x as &Any;
    (y.downcast_ref::<Handler<Cow<str>>>().unwrap().f)(&mut Cow::Borrowed(&*format!("{}", "Foo")));
}

Can we somehow make the latter code also work? (with whatever syntax we can come up with)

This would have important consequences for both futures and my eventbus, and we know the code is sound because the first variant works. Otherwise, I guess my only other option is something like this unsafe mess: (I believe futures or tokio or something has something like this)

struct UnsafelyForAnyLifetime<T> {
    v: *mut T
}
impl<T> UnsafelyForAnyLifetime<T> {
    /// unsafe because you need to put T somewhere on the stack
    unsafe fn new(t: &mut T) -> self {
        Self { v: t as *mut T }
    }
    /// safe because it follows the lifetime of the self, and thus can't
    /// escape an for<'a> Fn(&'a mut ...) (aka the intended usage)
    fn get_mut<'a>(&'a mut self) -> &'a mut T {
        unsafe { &mut *self.v }
    }
    /// safe because same as above
    fn get<'a>(&'a self) -> &'a T {
        unsafe { &*self.v }
    }
}

I’ll bite. First off, next time you ask for help, please please please don’t just paste a single long line of code. I’ve gone ahead and formatted it this time, but rustfmt could have done it for you. Also, I suspect this post is better suited for URLO rather than IRLO, but I digress.

use std::any::Any;
use std::borrow::Cow;
use std::sync::Arc;

struct Handler<T> {
    f: Arc<dyn for<'a> Fn(&'a mut T) + 'static>,
}

fn make_handler<T>(f: impl for<'a> Fn(&'a mut T) + 'static) -> Handler<T> {
    Handler { f: Arc::new(f) }
}

fn outline_fn_trait() {
    fn z<'a, 'b>(x: &'a mut Cow<'b, str>) {
        println!("{}", x);
    }
    let x: Handler<Cow<'?, str>> = make_handler(z);
    let y: &'? dyn Any = &x;

    (y.downcast_ref::<Handler<Cow<'?, str>>>().unwrap().f)
        (&mut Cow::Borrowed(&*format!("{}", "Foo")));
}

fn inline_fn_trait() {
    fn z<'a, 'b>(x: &'a mut Cow<'b, str>) {
        println!("{}", x);
    }

    let x: &'? (dyn for<'a> Fn(&'a mut Cow<'?, str>) + 'static) = &z;
    let y: &'? dyn Any = &x;
    (y.downcast_ref::<&(dyn for<'a> Fn(&'a mut Cow<'?, str>) + 'static)>().unwrap())
        (&mut Cow::Borrowed(&*format!("{}", "Foo")));
}

The fundamental problem is that Handler<T> contains a pointer to a closure that can take any &'a mut T; thus, the implicit bound for<'a> T: 'a. But, by union of lifetimes, this is equivalent to T: 'static. If you propacate this everywhere, you see that Handler<Cow<T>> must infer as Handler<Cow<'static, T>>. This explains the first error, which you can fix by constructing your Cow like this:

&mut Cow::Borrowed(
  Box::leak::<'static>(format!(..).into_boxed_str())
)

Edit: it strikes me as slightly odd that String doesn’t have a leak method directly… shrug.

There is no other way to fix this problem; if you want to stick a closure in Handler without making Handler generic in the longest lifetime that can be passed to f, then it can only take T: 'static types. I suspect he type you really want is

struct Handler<'ctx, T> where T: 'ctx {
  callback: Arc<dyn for<'a: 'ctx> Fn(&'a mut T) + 'ctx>,
}

where 'ctx is the lifetime of whatever thing the handler is being registered for. Presumably, the register method of your event loop or whatever should look like this:

pub fn register_handler(&mut self, &mut Handler<T>)

where all the lifetimes are deduced to be 'self.


Now, why does the second example work? I believe this is just with Fn being treated specially by the typechecker; if I instead wrote

type MyFn<T> = dyn for<'a> Fn(&'a mut T) + 'static;
(y.downcast_ref::<&MyFn<Cow<str>>>().unwrap())
        (&mut Cow::Borrowed(&*format!("{}", "Foo")));

we see the same error, since MyFn has an implicit where T: 'static clause. I suspect that the fact that this even worked in the first place is a bug, since I would expect dyn for<'a> Fn(&'a mut T) + 'static to impose the same where clause.

3 Likes

It's because it's working it out to for<'a, 'r> Fn(&'a mut Cow<'r, str>). It's definitely not a bug. (you can see this if you fudge with the function.)

I want a Handler<for<'a> Cow<'a, str>>. (syntax is up to debate ofc)

Also keep in mind any "solution" (which doesn't exist) that you might come up with needs to comply with Any (aka 'static). There's a very good reason why there's an Any in that code, and you can see it all here.

Please give us a way to take an type such as

type MyFn<T> = dyn for<'a> Fn(&'a mut T) + 'static;

and attach a generic to it such as

type MyFnT = MyFn<for<'a> Cow<'a, str>>;

such that it gets hoisted into

type MyFnT = dyn for<'a, 'other_a> Fn(&'a mut Cow<'other_a, str>);

AND THEN you have this:

fn main() {
    type MyFn<T> = dyn for<'a> Fn(&'a mut T) + 'static;
    type MyFnCowstr = dyn for<'a> Fn(&'a mut std::borrow::Cow<str>) + 'static;
    let foo: &MyFnCowstr = &|a| println!("{}", a);
    let bar: &MyFn<_> = foo;
    
    // this works
    bar(&mut std::borrow::Cow::Borrowed(&String::new()));
    
    let foo_any: &std::any::Any = &foo as &std::any::Any;
    let foo_back = foo_any.downcast_ref::<&MyFnCowstr>().unwrap();
    // this works
    foo_back(&mut std::borrow::Cow::Borrowed(&String::new()));
    
    // UNCOMMENT the following code to break the code above.
    /*
    let bar_any: &std::any::Any = &bar as &std::any::Any;
    // */
}

See also this related post in URLO by @Soni

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