Why does 'one type is more general than other' occur with the same type

I have been messing around with code for a while, and I found that this code can cause error

#[derive(Clone)]
pub struct Parser{
    f: Arc<dyn Fn(&str) -> Result<&str,&str>>
}

type Func = dyn Fn(&str) -> Result<&str,&str>;
#[allow(dead_code)]
impl Parser {
    fn and_then(&self,next:&Self) -> Self{
        let p1 = self.clone();
        let p2 = next.clone();
        let combined = move |s:&str|{
            let output:Result<&str,&str>;
            if let Ok(v) = p1.run(s) {
                output = p2.run(v);
                return output;
            }
            output = p1.run(s);
            output
        };
        Parser{
            f:Arc::new(combined)
        }
    }   
    fn run(&self,s1:&str) -> Result<&str,&str>
    {
        (self.clone().f)(s1)
    }
}

and the error message is

--> html_parser.rs:29:19
   |
17 |                 f:Arc::new(combined)
   |                   ^^^^^^^^^^^^^^^^^^ one type is more general than the other
   |
   = note: expected enum `Result<&str, &str>`
              found enum `Result<&str, &str>`

Which doesn't seems to make sense, why could the two similar enum type cause this error.
Is it possible to prevent this error in the future? thank you!

The lifetimes in and_then are bound by the lifetime of the self parameter. But you have no such bounds on Parser, which holds a function which must be valid for any possible lifetime. So the result of and_then cannot actually outlive the input, but you're trying to return something that is supposed to outlive the input.

Thus the Arc is expecting a more general type, with looser lifetime constraints than combined, which you've constructed according to the input lifetimes. You need to be more explicit about the lifetimes... You could make Parser have a lifetime parameter, or you can try to make and_then only function when the lifetimes of the inputs are compatible... I would probably do the former, as you're probably always parsing from a single source with a known lifetime, so I would just be explicit about what that lifetime is everywhere.

1 Like

By having a lifetime parameter, does it means that I have to add it into pub struct parser?

Errors like this can happen when you have a closure that is inferred to be tied to a specific lifetime instead of higher-ranked over a generic lifetime. It's a known short-coming of the current implementation, and yes, the error is confusing. The "more general than the other" is a hint that higher-ranked types are involved.

You can fix it by "forcing" the higher-ranked type -- by putting the closure in a context where the compiler expects a higher-ranked Fn implementation.

fn func_hrtb<F: Fn(&str) -> Result<&str, &str>>(f: F) -> F { f }
//              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// "Be higher-ranked (generic) over your input lifetime"
// Aside from enforcing that bound, this is just an identity function

// Elsewhere...

let combined = func_hrtb(move |s: &str| { /* ... */ });

Playground... but wait. Now we have an even bigger mess of errors! This time it's about a conflict between bounds ("cannot infer an appropriate lifetime for autoref due to conflicting requirements"), but a similar "Expected X, found X" note is still there. Now what?

Well, there's another error about the run method, which was also present the first time. Let's take a look at that:

    fn run(&self, s1: &str) -> Result<&str, &str> {
        (self.clone().f)(s1)
    }

Based on the lifetime elision rules, this function signature says:

    fn run<'s, 'a>(&'s self, s1: &'a str) -> Result<&'s str, &'s str> {

"Borrow me (self) for 's and I'll give you an 's-limited Result -- no matter the lifetime of s1!"

But looking at the implementation, you clone self (making the lifetime of the borrow of self irrelevant), and then call f with s1. And f has the signature:

Arc<dyn Fn(&str) -> Result<&str, &str>>
// i.e., implements...
Fn(&str) -> Result<&str, &str>
// i.e., via elision rules again
for<'any> Fn(&'any) -> Result<&'any, &'any>

Which is to say that elision gave pretty much the opposite of what we implemented -- the lifetime on self should be ignored, and the lifetime on s1 should be propagated to the Result. That is:

    fn run<'s>(&self, s1: &'s str) -> Result<&'s str, &'s str> {
        (self.clone().f)(s1)
    }

With that change, the example compiles. The mess of errors we got in the intermediate step were there because they were based on the signature of run (and not its implementation). The signature is the contract; when evaluating and_then, the compiler assumed run would do what the signature said it would. And that created a conflict in lifetime bounds that it couldn't resolve.


If you start from the OP and apply the changes in the opposite order, you don't get an intermediate mess, you just remove one error and then the other. I don't have any insight on how to choose which errors to address first in a general sense, though.

2 Likes

After letting that sit for a few minutes, actually, my advice is: when in doubt, address the errors that have to do with function signatures first, as errors there may cascade into errors at the call sites.

2 Likes

Your code work! thank you

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.