简体   繁体   中英

How to implement a trait for a function

I have the following code

let hazard_predicate = predicate::function(|x: &String| {
    if (x == "You got it right!" || x == "You got it wrong!") {
        return true;
    } else {
        return false;
    }
});
let mut cmd = Command::cargo_bin("rust-starter").expect("Calling binary failed");
cmd.arg("hazard").assert().stdout(hazard_predicate);

It doesn't compile. It complains that hazard_predicate doesn't implement a particular trait.

Here is the error message

error[E0277]: the trait bound 
 `predicates::function::FnPredicate<[closure@core/tests/test_cli.rs:31:48: 37:6], std::string::String>: assert_cmd::assert::IntoOutputPredicate<_>` is not satisfied
  --> core/tests/test_cli.rs:39:32
    |
 39 |     cmd.arg("hazard").assert().stdout(hazard_predicate);
   |                                ^^^^^^ the trait `assert_cmd::assert::IntoOutputPredicate<_>` is not implemented for `predicates::function::FnPredicate<[closure@core/tests/test_cli.rs:31:48: 37:6], std::string::String>`

 error: aborting due to previous error

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

So How do I implement that trait for my predicate function?

Let's look at the documentation for the types and traits in question. Usually the required traits are implemented automatically on all the types where it is possible, and, in fact, if the type in question is not your own, the trait has to be implemented by the library. So, first of all we check the assert_cmd docs to see what types can be used here.

There are two implementations which can be of interest for us:

impl<P> IntoOutputPredicate<StrOutputPredicate<P>> for P
where
    P: Predicate<str>
impl<P> IntoOutputPredicate<P> for P
where
    P: Predicate<[u8]>

Let's see now, what is the Predicate . This ends in the predicates-core crate, so it seems that at least some of the items from the predicates crate (based on this core) will be possible to use.

Now, let's try the other way round - look through the docs for predicate::function :

pub fn function<F, T>(function: F) -> FnPredicate<F, T> 
where
    F: Fn(&T) -> bool,
    T: ?Sized, 

Well then, we've got the type FnPredicate mentioned in the error message, so what traits are implemented by it?

impl<F, T> Predicate<T> for FnPredicate<F, T>
where
    F: Fn(&T) -> bool,
    T: ?Sized, 

Here it is! You've passed a closure taking &String , so the T in this definition is inferred to be String , ie the implemented trait is Predicate<String> .

Now, if you recall the first part, you'll see that there is no Predicate<String> there in implementations!

How to resolve this?

I see two possibilities, as for now:

  1. You can use the second implementation and make your predicate take reference to a byte slice &[u8] . I can't test it with the library itself, since it isn't on the playground, but if I make this change just in closure, I immediately get the error :
error[E0277]: can't compare `[u8]` with `str`
 --> src/lib.rs:3:15
  |
3 |         if (x == "You got it right!" || x == "You got it wrong!") {
  |               ^^ no implementation for `[u8] == str`
  |
  = help: the trait `std::cmp::PartialEq<str>` is not implemented for `[u8]`
  = note: required because of the requirements on the impl of `std::cmp::PartialEq<&str>` for `&[u8]`

Fortunately, this is fairly easily fixed by changing string literals to byte strings ( playground ):

let _ = |x: &[u8]| {
    x == b"You got it right!" || x == b"You got it wrong!"
};

Note that I also took advantage of Clippy hints to simplify the code in question (on playground it is under Tools button on the right side).

Now, if you pass this closure into predicate::function , all should work fine.

  1. Another way is to use the first implementation - you can see that Predicate<str> , ie function predicate receiving &str , is also supported, although in a bit more complex way. But for now this doesn't seem to be a problem, since the trait is implemented anyway - that's just one internal layer of indirection, but this is not your problem ( assert_cmd crate should handle this itself). This code, in particular, compiles well:
use assert_cmd::{assert::OutputAssertExt, cargo::CommandCargoExt};
use predicates::prelude::*;
use std::process::Command;

fn main() {
    let hazard_predicate =
        predicate::function(|x: &str| x == "You got it right!" || x == "You got it wrong!");
    let mut cmd = Command::cargo_bin("rust-starter").expect("Calling binary failed");
    cmd.arg("hazard").assert().stdout(hazard_predicate);
}

Side note

There is a long-standing question here describing why this is bad to have the functions require &String (or &Vec , or &Box - a reference to the owned container, it is). In short - you can replace &String by &str , and this will not be a restriction. Of course, library authors know that too and usually force you to have the most general way, where you have to work with the least indirection possible.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM