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 u8
s. 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.)
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::Reader
s 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.