asomers / mockall

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

Problem with mocking a trait which uses &Self #561

Closed avysk closed 7 months ago

avysk commented 7 months ago

Hello,

I have the following problem. I wonder what I am doing incorrectly. Here is the working code:

trait Foo {
    fn foo(&self) -> Option<&Self>;
}

impl Foo for str {
    fn foo(&self) -> Option<&Self> {
        if self.is_empty() {
            None
        } else {
            Some(&self[1..])
        }
    }
}

fn main() {
    println!("{:?} {:?}", "".foo(), "bar".foo());
}

This can be run and it prints, of course, None Some("ar"). Now I add in the beginning some lines:

use mockall::automock;

#[automock]

with the intention to make trait Foo mockable. However, the thing explodes:

❯ cargo build                                                                                                                                  Compiling predicates-core v1.0.6                                                                                                            Compiling cfg-if v1.0.0
   Compiling termtree v0.4.1
   Compiling anstyle v1.0.6
   Compiling fragile v2.0.0                                                                                                                    Compiling downcast v0.11.0
   Compiling lazy_static v1.4.0
   Compiling mockall_derive v0.12.1
   Compiling predicates-tree v1.0.9
   Compiling predicates v3.1.0
   Compiling mockall v0.12.1
   Compiling mocking v0.1.0 (/tmp/mocking)
error[E0106]: missing lifetime specifier
 --> src/main.rs:4:7
  |
4 | trait Foo {                                                                                                                               |       ^ expected named lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
help: consider using the `'static` lifetime, but this is uncommon unless you're returning a borrowed value from a `const` or a `static`
  |
4 | trait F'static oo {
  |        +++++++
help: instead, you are more likely to want to return an owned value
  |
4 - trait Foo {
4 + trait oo {
  |

error[E0106]: missing lifetime specifier
 --> src/main.rs:4:7
  |
4 | trait Foo {
  |       ^ expected named lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
help: consider using the `'static` lifetime, but this is uncommon unless you're returning a borrowed value from a `const` or a `static`
  |
4 | trait F'static oo {
  |        +++++++
help: instead, you are more likely to want to change one of the arguments to be borrowed...
  |
3 | &#[automock]
  | +
help: ...or alternatively, you might want to return an owned value
  |
3 | #[automock]
  |

Some errors have detailed explanations: E0106, E0637.
For more information about an error, try `rustc --explain E0106`.
error: could not compile `mocking` (bin "mocking") due to 3 previous errors

Removing &Self usage from the trait helps. The following compiles and works:

use mockall::automock;

#[automock]
trait Foo {
    fn foo(&self) -> usize;
}

impl Foo for str {
    fn foo(&self) -> usize {
        self.len()
    }
}

fn main() {
    println!("{:?} {:?}", "".foo(), "bar".foo());
}

Just in case my Cargo.toml:

[package]
name = "mocking"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
mockall = "0.12.1"

What am I doing incorrectly?

asomers commented 7 months ago

What am I doing incorrectly?

Nothing. This is a limitation of Rust and its lifetime system. Basically, Mockall needs for a mocked method's return value to be 'static. There are a few exceptions, for things like returning a reference. But supporting additional patterns like Option<&T> would require special code for every possible pattern.

I'm going to close this bug as a duplicate of #387 . You can see a workaround there.