rusttypestraits

Using generics together with async, cannot find method of struct


I am implementing a function in Rust that take a callback function as an argument. As far as I learned, I can use Fn trait bound to do that.

But now I want this callback function to be async, and I want to define a plain async fn function and use it as the argument, just like how Axum works.

Following is what have I have done so far:

pub struct Engine<T, Before, BeforeFut>
where
    Before: for<'a> Fn(&'a mut Option<T>) -> BeforeFut,
    BeforeFut: Future<Output = u32>,
{
    context: Option<T>,
    callback: Option<Before>,
}

impl<T, Before, BeforeFut> Engine<T, Before, BeforeFut>
where
    Before: for<'a> Fn(&'a mut Option<T>) -> BeforeFut,
    BeforeFut: Future<Output = u32>,
{
    pub fn set_callback(mut self, callback: Before) -> Self {
        self.callback = Some(callback);
        self
    }

    pub async fn run(mut self) {
        if let Some(f) = self.callback {
            f(&mut self.context).await;
        }
    }
}

async fn the_callback(context: &mut Option<TheContext>) -> u32 {
    context.as_ref().unwrap().counter + 1
}

struct TheContext {
    pub counter: u32,
}

#[tokio::main]
async fn main() {
    let mut engine = Engine {
        context: Some(TheContext { counter: 0 }),
        callback: Some(the_callback),
    };

    let result = engine.run().await;
    assert_eq!(result, 2);
}

This minimal code failed to compile:

error[E0599]: no method named `run` found for struct `Engine<TheContext, fn(&mut Option<TheContext>) -> ... {the_callback}, ...>` in the current scope
  --> src/main.rs:44:25
   |
2  | pub struct Engine<T, Before, BeforeFut>
   | --------------------------------------- method `run` not found for this struct
...
44 |     let result = engine.run().await;
   |                         ^^^ method not found in `Engine<TheContext, fn(&mut Option<TheContext>) -> ... {the_callback}, ...>`
   |
   = note: the method was found for
           - `Engine<T, Before, BeforeFut>`

This message confuses me since the method run is defined.


Solution

  • Your current error is shadowing the original issue, namely that the_callback() does not implement for<'a> Fn(&'a mut Option<T>) -> BeforeFut:

    pub struct Engine<T, Before, BeforeFut>
    where
        Before: for<'a> Fn(&'a mut Option<T>) -> BeforeFut,
        BeforeFut: Future<Output = u32>,
    {
        context: Option<T>,
        callback: Option<Before>,
    }
    
    impl<T, Before, BeforeFut> Engine<T, Before, BeforeFut>
    where
        Before: for<'a> Fn(&'a mut Option<T>) -> BeforeFut,
        BeforeFut: Future<Output = u32>,
    {
        pub fn set_callback(mut self, callback: Before) -> Self {
            self.callback = Some(callback);
            self
        }
    
        pub async fn run(mut self) {
            if let Some(f) = self.callback {
                f(&mut self.context).await;
            }
        }
    }
    
    async fn the_callback(context: &mut Option<TheContext>) -> u32 {
        context.as_ref().unwrap().counter + 1
    }
    
    struct TheContext {
        pub counter: u32,
    }
    
    #[tokio::main]
    async fn main() {
        let mut engine = Engine {
            context: Some(TheContext { counter: 0 }),
            callback: Some(the_callback),
        };
    
        //let result = engine.run().await;
        //assert_eq!(result, 2);
    }
    
       Compiling playground v0.0.1 (/playground)
    warning: unused variable: `engine`
      --> src/main.rs:37:13
       |
    37 |     let mut engine = Engine {
       |             ^^^^^^ help: if this is intentional, prefix it with an underscore: `_engine`
       |
       = note: `#[warn(unused_variables)]` on by default
    
    warning: variable does not need to be mutable
      --> src/main.rs:37:9
       |
    37 |     let mut engine = Engine {
       |         ----^^^^^^
       |         |
       |         help: remove this `mut`
       |
       = note: `#[warn(unused_mut)]` on by default
    
    error[E0308]: mismatched types
      --> src/main.rs:37:22
       |
    37 |       let mut engine = Engine {
       |  ______________________^
    38 | |         context: Some(TheContext { counter: 0 }),
    39 | |         callback: Some(the_callback),
    40 | |     };
       | |_____^ one type is more general than the other
       |
       = note: expected opaque type `impl for<'a> Future<Output = u32>`
                  found opaque type `impl Future<Output = u32>`
       = note: distinct uses of `impl Trait` result in different opaque types
    note: the lifetime requirement is introduced here
      --> src/main.rs:3:46
       |
    3  |     Before: for<'a> Fn(&'a mut Option<T>) -> BeforeFut,
       |                                              ^^^^^^^^^
    
    For more information about this error, try `rustc --explain E0308`.
    warning: `playground` (bin "playground") generated 2 warnings
    error: could not compile `playground` (bin "playground") due to 1 previous error; 2 warnings emitted
    

    You can use the new AsyncFn trait to specify your callback type:

    pub struct Engine<T, Before>
    where
        Before: for<'a> AsyncFn(&'a mut Option<T>) -> u32,
    {
        context: Option<T>,
        callback: Option<Before>,
    }
    
    impl<T, Before> Engine<T, Before>
    where
        Before: for<'a> AsyncFn(&'a mut Option<T>) -> u32,
    {
        pub fn set_callback(mut self, callback: Before) -> Self {
            self.callback = Some(callback);
            self
        }
    
        pub async fn run(mut self) {
            if let Some(f) = self.callback {
                f(&mut self.context).await;
            }
        }
    }
    
    async fn the_callback(context: &mut Option<TheContext>) -> u32 {
        context.as_ref().unwrap().counter + 1
    }
    
    struct TheContext {
        pub counter: u32,
    }
    
    #[tokio::main]
    async fn main() {
        let engine = Engine {
            context: Some(TheContext { counter: 0 }),
            callback: Some(the_callback),
        };
    
        let result = engine.run().await;
        assert_eq!(result, ());
    }
    

    Playground