Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement FnMut which returns reference with lifetime parameter?

Tags:

rust

I have third-party library with code like this:

pub struct Foo<T> { t: T }

impl<'a, T> Foo<T> where T: 'a + FnMut() -> &'a [u8] {
    pub fn from_callback(closure: T) -> Self {
        Foo { t: closure }
    }
}

It works fine with closure:

let buffer = vec![0; 100];
Foo::from_callback(move || &buffer)

however I want to return Foo from my function. Because of that I cannot use closure, so I have decided to use own struct which implements FnMut.

I have defined struct:

pub struct Func {
  buffer: Vec<u8>,
}

then implemented FnMut for it:

#![feature(unboxed_closures)]

impl FnMut<()> for Func {
  extern "rust-call" fn call_mut(&mut self, _: ()) -> Self::Output {
    &self.buffer
  }
}

implementing FnOnce is then required:

impl FnOnce<()> for Func {
  type Output = &[u8];
                ^~~~  error: missing lifetime specifier [E0106]
  extern "rust-call" fn call_once(self, _: ()) -> Self::Output {
    unimplemented!();
  }
}

But I don't know what to use as lifetime for reference in Output.

like image 418
kriomant Avatar asked Oct 24 '25 06:10

kriomant


2 Answers

@MatthieuM. is correct. This can't be done without higher-kinded types, and even if they were available, I'm not entirely sure that Fn* traits could be changed to support it.

The problem is, you want your function to return a slice of a vector inside this function. However, this would require call_mut() to have the following signature:

fn call_mut<'a>(&'a mut self, _: ()) -> Self::Output<'a>

The lifetime parameter is mandatory, because it is the only way to specify that the result of the function call will live exactly as long as the function itself. This would in turn require Output associated type to have a lifetime argument:

trait FnOnce<A> {
    type Output<'a>;
    ...
}

This is exactly what HKT would allow. However, I'm not sure that it would be possible to use HKT for functions. Remember, each FnMut is also FnOnce: if you have a function which can be called multiple times, then it could also be called one time. Naturally, results of FnMut and FnOnce should be equivalent. However, there is just no way to return borrowed data from FnOnce because it takes the function by value. In your case the vector would be moved to the function and, since the function returns a slice, not a vector, it will have nowhere to go and so it will be destroyed. Therefore, such change would require allowing some functions to be FnMut/Fn but not FnOnce; I'm not sure if this is desirable or even possible.

like image 195
Vladimir Matveev Avatar answered Oct 26 '25 19:10

Vladimir Matveev


This problem can be solved by using a trait itself to express the would-be function.

trait CallbackTrait {
    fn call(&mut self) -> &[u8];
}

fn make_callback() -> impl CallbackTrait {
    struct MyData(Vec<u8>);
    impl CallbackTrait for MyData {
        fn call(&mut self) -> &[u8] { &self.0 }
    }
    MyData(vec![0; 100])
}

And, when you want it to be a bit more generically passed around, it can be boxed.

fn make_another_callback() -> Box<dyn MyCallback> {
    Box::new<make_callback()>
}

Seeing as no API expects any Fn(Once|Mut|) that returns something with the lifetime of a function itself, the problem itself is an issue of framing. A situation where you might expect to use this functionality can instead use a trait with a small change to the approach, with the added benefit of much more flexibility like requiring other traits, or having multiple functions. As for why the language itself doesn't support this concept, @Vladimir Matveev has that explanation.

like image 27
Wesley Wolfe Avatar answered Oct 26 '25 20:10

Wesley Wolfe