Can't compare `&Thing` with `Thing`

2019-06-14 21:27发布

问题:

I know what the error means, but I can't fix it. I'm using mockers to test my work and I got stuck when trying to verify the struct parameter which was given to a mocked trait's function. The simplified code:

#[cfg(test)]
extern crate mockers;
#[cfg(test)]
extern crate mockers_derive;

#[cfg(test)]
use mockers_derive::mocked;

#[derive(Ord, PartialOrd, Eq, PartialEq, Debug)]
pub struct Thing {
    pub key: String,
    pub class: String,
}

#[cfg_attr(test, mocked)]
pub trait DaoTrait {
    fn get(&self, thing: &Thing) -> String;
}

struct DataService {
    dao: Box<DaoTrait>,
}

impl DataService {
    pub fn get(&self, thing: &Thing) -> String {
        self.dao.get(thing)
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use mockers::matchers::eq;
    use mockers::Scenario;

    #[test]
    fn my_test() {
        use mockers::matchers::check;
        let scenario = Scenario::new();
        let mut dao = scenario.create_mock_for::<DaoTrait>();
        let thing = Thing {
            key: "my test".to_string(),
            class: "for test".to_string(),
        };

        scenario.expect(
            dao.get_call(check(|t: &Thing| t.to_owned() == thing))
                .and_return("hello".to_string()),
        );
        let testee = DataService { dao: Box::new(dao) };

        let rtn = testee.get(&thing);
        assert_eq!(rtn, "hello");
    }
}

I got the errors:

warning: unused import: `mockers::matchers::eq`
  --> src/main.rs:33:9
   |
33 |     use mockers::matchers::eq;
   |         ^^^^^^^^^^^^^^^^^^^^^
   |
   = note: #[warn(unused_imports)] on by default

error[E0277]: can't compare `&Thing` with `Thing`
  --> src/main.rs:47:57
   |
47 |             dao.get_call(check(|t: &Thing| t.to_owned() == thing))
   |                                                         ^^ no implementation for `&Thing == Thing`
   |
   = help: the trait `std::cmp::PartialEq<Thing>` is not implemented for `&Thing`

error[E0277]: the trait bound `mockers::matchers::BoolFnMatchArg<Thing, [closure@src/main.rs:47:32: 47:65 thing:_]>: mockers::MatchArg<&Thing>` is not satisfied
  --> src/main.rs:47:17
   |
47 |             dao.get_call(check(|t: &Thing| t.to_owned() == thing))
   |                 ^^^^^^^^ the trait `mockers::MatchArg<&Thing>` is not implemented for `mockers::matchers::BoolFnMatchArg<Thing, [closure@src/main.rs:47:32: 47:65 thing:_]>`
   |
   = help: the following implementations were found:
             <mockers::matchers::BoolFnMatchArg<T, F> as mockers::MatchArg<T>>

I viewed check's source code :

pub fn check<T, F: Fn(&T) -> bool>(f: F) -> BoolFnMatchArg<T, F> {
    BoolFnMatchArg { func: f, _phantom: PhantomData }
}

I think the closure |t: &Thing| t.to_owned() == thing I had given is right. I also tried the following closures, but none of them worked.

|t: &Thing| t == &thing
|t: &Thing| *t == thing
|t: Thing| t == thing

The Cargo.toml:

[dev-dependencies]
mockers = "0.12.1"
mockers_derive = "0.12.1"

回答1:

You cannot compare a Thing to a &Thing using the default derivation of PartialEq:

#[derive(Debug, PartialEq)]
struct Thing(String);

fn main() {
    let t_val = Thing(String::new());
    let t_ref = &t_val;

    t_val == t_ref;
}
error[E0308]: mismatched types
 --> src/main.rs:8:14
  |
8 |     t_val == t_ref;
  |              ^^^^^ expected struct `Thing`, found &Thing
  |
  = note: expected type `Thing`
             found type `&Thing`

To fix that error, you need to do one of two things:

  1. Match the reference level:

    • t_val == *t_ref

    • &t_val == t_ref

  2. Implement equality for a mismatched number of references:

    impl<'a> PartialEq<&'a Thing> for Thing {
        fn eq(&self, other: &&'a Thing) -> bool {
            self == *other
        }
    }
    
    impl<'a> PartialEq<Thing> for &'a Thing {
        fn eq(&self, other: &Thing) -> bool {
            *self == other
        }
    }
    

However, none of that solves your actual problem. You've misunderstood how the mockers library works; your closure is taking the wrong level of reference, and it needs to take ownership of the value to compare:

let expected_thing = thing.clone();
scenario.expect(
    dao.get_call(check(move |t: &&Thing| t == &&expected_thing))
        .and_return("hello".to_string()),
);


回答2:

The first thing (pun not intended) to notice is that t.to_owned() produces a &Thing, not a Thing as you might have expected. That's because Thing doesn't implement Clone, and therefore it doesn't implement ToOwned either (because there is a blanket impl that implements ToOwned for all Clone types), which provides the to_owned method. But then why does the call still work? Because references implement Clone, so &Thing implements ToOwned! This gives to_owned this signature:

fn to_owned(self: &&Thing) -> &Thing;

You can fix this by deriving Clone for Thing.

However, you don't need to clone the Thing in order to compare it. You can instead compare two references to Thing (e.g. by writing |t: &Thing| t == &thing). PartialEq::eq (which the == operator translates to) takes its arguments by reference, and references implement PartialEq by peeling off a layer of references (i.e. they don't compare the pointer values, unlike raw pointer types).



标签: rust mocking