I'd like to wrap a function in a struct to package it with other data. When I specify the input type of the function explicitly, it works fine. When I attempt to generalize the pattern into a generic struct, I run into lifetime errors.
I've shared a code snippet in a playground and below. As is this does not compile. If I switch from Wrapper to WrapperExplicit, it works perfectly. What's going wrong? How I can make this wrapper generic?
use std::marker::PhantomData;
struct Reference<'a, X> {
x : & 'a mut X
}
//Works
struct WrapperExplicit<F>
where F : Fn(Reference<String>)
{
data : u64,
func : F,
}
impl<F> WrapperExplicit<F>
where F : Fn(Reference<String>)
{
pub fn new(func: F) -> WrapperExplicit<F> {
WrapperExplicit {
data : 10,
func: func,
}
}
pub fn call(& mut self, arg: Reference<String>)
{
(self.func)(arg)
}
}
//Doesn't work
struct Wrapper<F, U>
where F : Fn(U)
{
data : u64,
func : F,
_marker: PhantomData<U>,
}
impl<F,U> Wrapper<F,U>
where F : Fn(U)
{
pub fn new(func: F) -> Wrapper<F,U> {
Wrapper {
data : 0,
func: func,
_marker: PhantomData {}
}
}
pub fn call(& mut self, arg: U)
{
(self.func)(arg)
}
}
fn fn_borrow(x : Reference<String>) {
println!("{}", x.x);
}
fn fn_move(y : String) {
println!("{}", &y);
}
pub fn test_example() {
let mut f = Wrapper::new(fn_borrow); //Doesn't work
// let mut f = WrapperExplicit::new(fn_borrow); //Works
let mut y = "Hello!".to_string();
let x = Reference{ x : &mut y};
f.call(x);
fn_move(y);
println!("{}", f.data)
}
EDIT: Here's an even simpler example: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=0f4f5236d4e4439b3bec5a717f62567c
I believe the issue is that because the generic type has to be set when you create the struct, the lifetime for the generic type is set then as well, so you can't use a different lifetime when you call the struct's associated function. Is that true? Is there a way to let the "type" part of the struct be generic, but leave the lifetime to be decided when one of the struct's functions is called?
use std::marker::PhantomData;
//Doesn't work
struct WrapperSimple<U>
{
data : u64,
_marker: PhantomData<U>,
}
impl<U> WrapperSimple<U>
{
pub fn new() -> WrapperSimple<U> {
WrapperSimple {
data : 0,
_marker: PhantomData {}
}
}
pub fn call(& mut self, _arg: U)
{
}
pub fn call2<V>(& mut self, _arg: V)
{
}
}
fn fn_move(y : String) {
println!("{}", &y);
}
fn main() {
let mut w = WrapperSimple::<&str>::new();
let mut y = "Hello!".to_string();
let x = & mut y;
w.call(x); //Doesn't work
//w.call2(x); //Works, (obviously)
fn_move(y);
println!("{}", w.data)
}
EDIT 2:
I think here's how it ultimately shakes out:
- You can't store a generic function with generic inputs in a struct without referencing the function's input type in the struct's type parameters, and therefore also need a PhantomData field of that type (otherwise you have an unconstrained or unused type parameter)
- Once you have a field of that type, the type's embedded lifetime has to be set when the struct is monomorphized, for example at construction
- That means the lifetime is fixed and cannot vary each time you call one of the struct's methods
- You also can't create a new generic type parameter for the method and constrain it to match the struct's type parameter, except stripping the struct type's lifetime
Therefore, what I want to accomplish is not currently supported by rust's type system. Please let me know if one of my assumptions here is wrong.