asomers / mockall

A powerful mock object library for Rust
Apache License 2.0
1.5k stars 62 forks source link

Anonymous lifetime in trait method #541

Closed dr-kernel closed 9 months ago

dr-kernel commented 9 months ago

I have an issue related to #411 and perhaps #87 where t the <`_> is in the trait signature (in this case using the tokio_modbus) and thus get a compile error when i try to add <'a> to the member. Is there a way around this or is it just not supported?

error[E0106]: missing lifetime specifier
   --> mycode.rs
    |
    |                 async fn call(&mut self, request: Request<'_>) -> Result<Response, std::io::Error> {
    |                                                           ^^ expected named lifetime parameter

after putting in the <`a>:

error[E0195]: lifetime parameters or bounds on method `call` do not match the trait declaration
   --> mycode.rs
    |
    |                 async fn call(&mut self, request: Request<'a>) -> Result<Response, std::io::Error> {
    |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ lifetimes do not match method in trait

Attempted mock:

mock! {
            #[allow(dead_code)]
            #[derive(Debug)]
            Context { }

            #[async_trait]
            impl Client for Context {
                async fn call(&mut self, request: Request<'a>) -> Result<Response, std::io::Error> { }
            }
            impl SlaveContext for Context {
                fn set_slave(&mut self, slave: Slave) { }
            }
        }
asomers commented 9 months ago

Could you please post a complete example?

dr-kernel commented 9 months ago

A more complete example of how the create i'm attempting to mock is used or how i would use it in a test? Or both?

asomers commented 9 months ago

A minimal example that demonstrates your problem. The one you posted above is incomplete.

dr-kernel commented 9 months ago

Please let me know if any further clarification is required. Thank you!

use std::io::Error;
use tokio_modbus::prelude::*;
use mockall::*;
use async_trait::async_trait;
mock! {
    #[allow(dead_code)]
    #[derive(Debug)]
    Context {}

    #[async_trait]
    impl Client for Context {
        async fn call(&mut self, request: Request<'_>) -> Result<Response, std::io::Error> {}
    }
    impl SlaveContext for Context {
        fn set_slave(&mut self, slave: Slave) {}
    }
}
fn main() -> Result<(), Error> {
    let mock = MockContext::new();
    let v = vec![1234u16];
    mock.returning(v);
    let reg = mock.read_input_registers(1u16, 1);
    assert_eq!(reg, v);
    Ok(())
}
asomers commented 9 months ago

So Mockall can't yet handle the anonymous lifetime in an argument position. It could with some extra work. But it usually doesn't need to, because when using mock! the user always has the option to give the lifetime a name. In this case, it's hard for you to name the lifetime, because async_trait adds some extra lifetimes. So in order to make this work, you have to use cargo expand to see what async_trait is actually doing. But once you've done that, you can easily make it work, like this:

use async_trait::async_trait;
use mockall::*;
use std::io::Error;
use tokio_modbus::prelude::*;

use core::future::Future;
use std::pin::Pin;

mock! {
    #[allow(dead_code)]
    #[derive(Debug)]
    Context {}

    #[async_trait]
    impl Client for Context {
        fn call<'life0, 'life1, 'async_trait>(
            &'life0 mut self,
            request: Request<'life1>,
        ) -> Pin<
                Box<dyn Future<Output = Result<Response, Error>> + Send + 'async_trait>
            >
        where
            'life0: 'async_trait,
            'life1: 'async_trait,
            Self: 'async_trait,
        {}
    }
    impl SlaveContext for Context {
        fn set_slave(&mut self, slave: Slave) {}
    }
}
dr-kernel commented 9 months ago

Thank you for that explanation. I'll look into cargo expand, I saw it in the playground but hadn't installed the tool locally. Appreciate the your knowledge. That does assist the compiler. I'll keep this in mind for the future!