rustlifetime

How to specify lifetime for associated type that will be a closure argument?


I have a trait with a function, and this function takes a closure as an argument, and that closure takes an argument that needs to be some type implementing the Read trait:

trait CanRead {
    type Reader: io::Read;
    
    fn do_reading<F>(&mut self, fun: F)
    where F: FnOnce(&mut Self::Reader);
}

I can easily implement this trait for anything which already implements Read, by specifying type Reader = Self; and just running fun(self); in the do_reading function.

The challenge is, I want to also implement this trait for some type which must make a temporary vector of u8s. Then the associated type Reader needs to be a reference type, but I don't know what lifetime to give it:

pub struct EmptyStruct { }

impl CanRead for EmptyStruct {
    type Reader = &[u8]; // doesn't compile; must specify a lifetime here
    
    fn do_reading<F>(&mut self, fun: F)
    where F: FnOnce(&mut Self::Reader) {
        let temp = vec![1, 2, 3];
        fun(&mut &temp[..]);
    }
}

I know that I need to specify a lifetime, but what could it be? I looked at this helpful related question, but neither suggested approach works. The problem is that the lifetime of the Reader type actually has nothing to do with the lifetime of the EmptyStruct instance; instead, the Reader reference type just needs to not outlive the call to the closure itself. Is there some way to specify this in Rust, or another way to tackle this pattern?

playground has what I tried and didn't work.

(Note, I know for this specific code the temp vector could be replaced by a static array, but that won't work for what I really need to do.)


Solution

  • Since the question was asked, advances in Rust now make this solvable. You need to use a generic associated type:

    pub trait CanRead {
        type Reader<'a>: io::Read where Self: 'a;
    
        fn do_reading<F>(&mut self, fun: F)
        where for<'a> F: FnOnce(&mut Self::Reader<'a>);
    }
    

    Here, the associated type Reader<'a> takes a lifetime parameter, making it generic. There is a lifetime bound where Self: 'a, stating that the lifetime of the parameter must be shorter (or equal to) the lifetime of the object that implements CanRead (you can read it as "where Self outlives 'a"); this lifetime bound is standard on generic associated types, because it is generally trivially true (methods of types that implement CanRead aren't likely to try to use Reader without a value of that type to work with) and makes it easier for the compiler to reason about the program.

    Now that Reader is generic, do_reading can now specify that fun has to be able to work with all lifetimes of reader, not just one specific lifetime: for<'a> means "for all lifetimes 'a", so the where clause on do_reading can be read as "where F is a FnOnce that can handle arguments that are mutable references to Self::Readers with arbitrary lifetimes". (OK, so do_reading probably needs the lifetime of the Self::Reader to last at least for the body of the do_reading call, but Rust automatically interprets the declaration as including the requirement.)

    With this definition of CanRead, it's possible to implement do_reading like this:

    impl CanRead for EmptyStruct {
        type Reader<'a> = &'a [u8] where Self: 'a;
        
        fn do_reading<F>(&mut self, fun: F)
        where for<'a> F: FnOnce(&mut Self::Reader<'a>) {
            let temp = vec![1, 2, 3];
            fun(&mut &temp[..]);
        }
    }
    

    This is basically the same as what you had in the playground – I had to change the definition of Reader and signature of do_reading to match the trait, but the "obvious" code works correctly in the body of the function.