DonaldWhyte / double

Mocking framework for Rust -- inspired by googlemock
MIT License
109 stars 6 forks source link

Returning &Self #9

Open U007D opened 6 years ago

U007D commented 6 years ago

Given the following trait:

pub trait Chal {
    fn set_mech_brakes(&self, amt: BrakeAmt) -> &Self;
    fn mech_brakes(&self) -> BrakeAmt;
}

I have tried:

mock_trait!(ChalMock,
            set_brakes(BrakeAmt) -> &Self,
            brakes() -> BrakeAmt);

impl Chal for ChalMock {
    mock_method!(set_mech_brakes(&self, amt: BrakeAmt) -> &Self);
    mock_method!(mech_brakes(&self) -> BrakeAmt);
}

but this yields:

error[E0411]: cannot find type `Self` in this scope
 --> shal_proto/src/unit_tests.rs:7:38
  |
7 |             set_brakes(BrakeAmt) -> &Self,
  |                                      ^^^^ `Self` is only available in traits and impls

error[E0106]: missing lifetime specifier
 --> shal_proto/src/unit_tests.rs:7:37
  |
7 |             set_brakes(BrakeAmt) -> &Self,
  |                                     ^ expected lifetime parameter

error: aborting due to 2 previous errors

I tried:

mock_trait!(ChalMock,
            set_brakes(BrakeAmt) -> &ChalMock,
            brakes() -> BrakeAmt);

impl Chal for ChalMock {
    mock_method!(set_mech_brakes(&self, amt: BrakeAmt) -> &ChalMock);
    mock_method!(mech_brakes(&self) -> BrakeAmt);
}

which helps, but I am still left with:

error[E0106]: missing lifetime specifier
 --> shal_proto/src/unit_tests.rs:7:37
  |
7 |             set_brakes(BrakeAmt) -> &ChalMock,
  |                                     ^ expected lifetime parameter

error: aborting due to previous error

How can I make a mock of Chal using double?

DonaldWhyte commented 6 years ago

Thanks for pointing this out!

Unfortunately, Rust's hygenic macros prevent us from being able to mock traits/methods which use the Self type, since the compiler can't deduce the meaning Self when it's used in a macro. I'll properly document that limitation and create a GitHub issue to track research into resolving it.

As for the second compiler error, I don't have an immediate explanation/solution. I will look into this tomorrow evening (UTC) and update this issue with whatever I find.

U007D commented 6 years ago

Thanks for the reply, @DonaldWhyte!

The Self issue seems to be something I can work around simply by supplying the type explicitly as I have done above.

For the lifetime issue, the compiler doesn't know that the lifetime of the reference being returned is tied to that of the (implied) &self argument, and because that argument is implied, there doesn't seem to be a way to explain this relationship to the compiler. (The practice of returning &Self is common when creating fluent API's.)

Hopefully some kind of workaround will spring to mind for the lifetimes issue! Fingers crossed... :)

U007D commented 6 years ago

Hi, @DonaldWhyte. Checking in to see if there is any news before I try crafting a home-grown workaround?

DonaldWhyte commented 6 years ago

Hey @U007D! I haven't forgotten about this. I've been spending most of the time I have available on double on implementing a concise/expressive call pattern matching API (inspired by Google Mock's).

That'll land into master and be deployed within the next couple of days, so I can begin to look at this again.

Recapping the two issues, we have:

  1. cannot use Self in traits you wish to mock
  2. Not being able to mock functions that return references

Unfortunately, I think solving (1) is out of reach at the time of writing. I believe Rust's hygenic macros are not sophisticated enough for this. There's a probably a hacky workaround that half solves the problem, but I think solving (2) is a higher priority.

(2) is a big problem. Currently, any functions that return references cannot be mocked. My proposal for this is for the generated mock (there's one Mock object per trait method) to be aware that it returns a reference. Mock already owns the values it's configured to return. It can simply return a borrowed reference to the configured return value it owns.

There are some subtleties and corner cases to handle. These are the cases where Mocks return values are constructed by functions or closures. The value is not known in advance and is thus, not owned by the Mock. This can be handled though. I'll spec out a prototype PR sometime in the next 1-3 weeks.

U007D commented 6 years ago

Thank you for the update, @DonaldWhyte! Yes, those are the two main issues. I do hope the Self issue is solvable, but I agree with you in that not being able to return refs is probably the better one to solve first.

I look forward to the PR once you have it ready! Thanks again for the update.

kaiba696 commented 6 years ago

+1 on this.. also curious about supporting mocking for traits with associated types (https://doc.rust-lang.org/book/second-edition/ch19-03-advanced-traits.html)