simulacrum icon indicating copy to clipboard operation
simulacrum copied to clipboard

Mock methods can't return by move

Open asomers opened this issue 7 years ago • 1 comments

simulacrum_mock::method::TrackedMethod::returning takes an FnMut closure, from which captured variables cannot be moved. Captured variables can only be moved out of FnOnce closures. This is a problem, because it makes it impossible to return a non-constant, non-Clone value from an expectation.

Sample code:

/// A handy type that is non-Clone and non-Copy
#[derive(Debug, Eq, PartialEq)]
pub struct UniquelyOwned(u32);

#[test]
fn return_owned() {
    pub trait A {
        fn foo(&self) -> UniquelyOwned;
    }

    create_mock! {
        impl A for AMock (self) {
            expect_foo("foo"):
            fn foo(&self) -> UniquelyOwned;
        }
     }

    let mut mock = AMock::new();
    let res = UniquelyOwned(42);
    mock.expect_foo().called_once().returning(move |_| res);

    assert_eq!(UniquelyOwned(42), mock.foo());
}

And the compile error:

error[E0507]: cannot move out of captured outer variable in an `FnMut` closure
   --> src/t_simulacrum.rs:521:60
    |
520 |         let res = UniquelyOwned(42);
    |             --- captured outer variable
521 |         mock.expect_foo().called_once().returning(move |_| res);
    |                                                            ^^^ cannot move out of captured outer variable in an `FnMut` closure                                               

error: aborting due to previous error

For more information about this error, try `rustc --explain E0507`.

Mock_Derive's solution is to store the result uniquely in the expectation, in an Option or something. It is then a runtime error to call that method twice without putting another return value into it. For expectations that need to be called multiple times, then must compute their return values by means of a closure instead.

Mockers' solution is similar.

asomers avatar May 27 '18 23:05 asomers

BTW, I've been using a workaround that looks like this. It's basically what mockers and Mock_Derive do internally.

let mut mock = AMock::new();
let res = Some(UniquelyOwned(42));
mock.expect_foo().called_once().returning(move |_| res.take().unwrap());

asomers avatar Sep 20 '18 18:09 asomers