Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mock trait with methods that return iterators #547

Open
vitornesello opened this issue Jan 29, 2024 · 8 comments
Open

Mock trait with methods that return iterators #547

vitornesello opened this issue Jan 29, 2024 · 8 comments
Labels
question Usage question

Comments

@vitornesello
Copy link

Hello,

I am trying to mock a trait with two iterator methods. iter and iter_mut, each of which returns an iterator to references and mutable references of a parametric type.

The code below does not compile, but the problem comes from the iter_mut method. The other one works just fine.

I could not find an example in the tests that resemble what I am trying to do.

#[automock(type T=String;)]
trait Foo {
    type T;
    fn iter(&self) -> impl Iterator<Item = usize>;
    fn iter_mut(&mut self) -> impl Iterator<Item = &mut Self::T>;
}

the compiler raises the following errors:

playground-rs on  master +/- [?] is 📦 v0.1.0 via 🦀 v1.75.0
❯ cargo build
   Compiling playground-rs v0.1.0 (/Users/vitornesello/code/playground-rs)
error[E0106]: missing lifetime specifier
 --> src/main.rs:3:19
  |
3 | #[automock(type T=String;)]
  |                   ^ 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
  |
3 | #[automock(type T=S'static tring;)]
  |                    +++++++

error[E0637]: `&` without an explicit lifetime name cannot be used here
 --> src/main.rs:3:19
  |
3 | #[automock(type T=String;)]
  |                   ^ explicit lifetime name needed here
  |
help: consider introducing a higher-ranked lifetime here
  |
3 ~ #[automock(type T=S'a tring;)]
4 | trait Foo {
5 |     type T;
6 |     fn iter(&self) -> impl Iterator<Item = usize>;
7 ~     fn iter_mut(&mut self) -> impl for<'a> Iterator<Item = &mut Self::T>;
  |

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

@asomers
Copy link
Owner

asomers commented Jan 29, 2024

This is a complicated trait. Does it work if you define that method like this? fn iter_mut(&mut self) -> impl Iterator<Item = &'static mut Self::T>;

@vitornesello
Copy link
Author

with this modification, I have the following error:

   Compiling playground-rs v0.1.0 (/Users/vitornesello/code/playground-rs)
error[E0310]: the associated type `<Self as Foo>::T` may not live long enough
 --> src/main.rs:8:45
  |
8 |     fn iter_mut(&mut self) -> impl Iterator<Item = &'static mut Self::T>;
  |                                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |                                             |
  |                                             the associated type `<Self as Foo>::T` must be valid for the static lifetime...
  |                                             ...so that the reference type `&'static mut <Self as Foo>::T` does not outlive the data it points at
  |
  = help: consider adding an explicit lifetime bound `<Self as Foo>::T: 'static`...

@asomers
Copy link
Owner

asomers commented Jan 29, 2024

Did you try following that suggestion and adding the bound?

@vitornesello
Copy link
Author

I could make it work like this, but I not sure if it is exactly what the suggestion was saying:

#[automock(type T=String;)]
trait Foo {
    type T: 'static;
    fn iter(&self) -> impl Iterator<Item = usize>;
    fn iter_mut(&mut self) -> impl Iterator<Item = &'static mut <Self as Foo>::T>;
}

However, it seems odd to me to require a static lifetime in this trait. Am I missing something?

@asomers
Copy link
Owner

asomers commented Jan 29, 2024

That's actually not as odd as you might think. In order to store the expectation, Mockall requires that the lifetime of return values be either the same as the object itself, or else 'static. And "the same as the object itself" only works for a few common cases like &T.
https://docs.rs/mockall/latest/mockall/#reference-return-values

@asomers asomers added the question Usage question label Jan 29, 2024
@vitornesello
Copy link
Author

Thanks a lot! I realize that I need a deeper understanding of lifetimes. I am quite new to the concept.

@mrchilliballs
Copy link

That's actually not as odd as you might think. In order to store the expectation, Mockall requires that the lifetime of return values be either the same as the object itself, or else 'static. And "the same as the object itself" only works for a few common cases like &T. https://docs.rs/mockall/latest/mockall/#reference-return-values

Could you consider clarifying what "the same as the object itself" means in the documentation? I stumbled upon the issue a while ago, and clearer documentation would have been helpful. Thanks!

@asomers
Copy link
Owner

asomers commented Feb 22, 2024

When mocking a function that returns a reference, Mockall stores that referent within the mock object. For example:

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

#[test]
fn t() {
    let mut mock = MockFoo::new();
    mock.expect_foo()
        .return_ref(42i32);
}

As you can see, we store a real i32 within the Mock object. So that i32 must have that same lifetime as the mock object itself. Mockall also has a few hard-coded special cases for methods that return references. For example, a method that returns a &str reference will take a String argument in its return_ref function. And if the function returns a 'static reference, then you can pass any anything you want to return_const or returning.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Usage question
Projects
None yet
Development

No branches or pull requests

3 participants