Conflicting lifetime requirements when wrapping cssparser methods

I have some code that wraps cssparser's Parser in a struct that does some additional bookkeeping. This struct wraps many of the methods from cssparser to do some work and then call the cssparser implementation.

I've run into some weird lifetime conflicts when wrapping methods that are only slightly different from ones that do work, and I'm having a hard time figuring out what to do about the errors.

A very simplified example of the wrapper:

struct Temp<'p, 'i, 't>(&'p mut Parser<'i, 't>);

An example of an impl that produces errors:

impl<'p, 'i, 't> Temp<'p, 'i, 't> {
    fn parse_nested_block<F, T, E>(&mut self, parse: F) -> Result<T, ParseError<'i, E>>
    where
        F: for<'q> FnOnce(&mut Temp<'q, 'i, 't>) -> Result<T, ParseError<'i, E>>,
    {
        let p = &mut self.0;

        p.parse_nested_block(|p| {
            let parser = Temp(p);
            parse(&mut parser)
        })
    }
}
Error output
error[E0495]: cannot infer an appropriate lifetime for lifetime parameter `'i` due to conflicting requirements
  --> src/main.rs:13:11
   |
13 |         p.parse_nested_block(|p| {
   |           ^^^^^^^^^^^^^^^^^^
   |
note: first, the lifetime cannot outlive the lifetime `'i` as defined here...
  --> src/main.rs:6:10
   |
6  | impl<'p, 'i, 't> Temp<'p, 'i, 't> {
   |          ^^
note: ...so that the types are compatible
  --> src/main.rs:13:11
   |
13 |         p.parse_nested_block(|p| {
   |           ^^^^^^^^^^^^^^^^^^
   = note: expected `&mut Parser<'_, '_>`
              found `&mut Parser<'i, 't>`
note: but, the lifetime must be valid for the anonymous lifetime #2 defined here...
  --> src/main.rs:13:30
   |
13 |           p.parse_nested_block(|p| {
   |  ______________________________^
14 | |             let parser = Temp(p);
15 | |             parse(&mut parser)
16 | |         })
   | |_________^
note: ...so that the expression is assignable
  --> src/main.rs:14:31
   |
14 |             let parser = Temp(p);
   |                               ^
   = note: expected `&mut Parser<'_, '_>`
              found `&mut Parser<'_, '_>`

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

And a similar impl which works fine:

impl<'p, 'i, 't> Temp<'p, 'i, 't> {
    fn try_parse<F, T, E>(&mut self, func: F) -> Result<T, ParseError<'i, E>>
    where
        F: for<'q> FnOnce(&mut Temp<'q, 'i, 't>) -> Result<T, ParseError<'i, E>>,
    {
        let inner = &mut self.0;

        inner.try_parse(|p| func(&mut Temp(p)))
    }
}

The only difference I can see between the cssparser signatures of those methods is that parse_nested_block uses a HRTB 'tt lifetime in place of 't. Updating the wrapper to do the same changes the location of the error but the conflicts are the same, just for 'tt now

impl<'p, 'i, 't> Temp<'p, 'i, 't> {
    fn parse_nested_block<F, T, E>(&mut self, parse: F) -> Result<T, ParseError<'i, E>>
    where
        F: for<'q, 'tt> FnOnce(&mut Temp<'q, 'i, 'tt>) -> Result<T, ParseError<'i, E>>,
    {
        let p = &mut self.0;

        p.parse_nested_block(|p| {
            let parser = Temp(p);
            parse(&mut parser)
        })
    }
}
Error output
error[E0495]: cannot infer an appropriate lifetime for lifetime parameter 'tt in generic type due to conflicting requirements
  --> src/main.rs:15:13
   |
15 |             parse(&mut parser)
   |             ^^^^^^^^^^^^^^^^^^
   |
note: first, the lifetime cannot outlive the lifetime `'i` as defined here...
  --> src/main.rs:6:10
   |
6  | impl<'p, 'i, 't> Temp<'p, 'i, 't> {
   |          ^^
note: ...so that the declared lifetime parameter bounds are satisfied
  --> src/main.rs:15:13
   |
15 |             parse(&mut parser)
   |             ^^^^^^^^^^^^^^^^^^
note: but, the lifetime must be valid for the anonymous lifetime #2 defined here...
  --> src/main.rs:13:30
   |
13 |           p.parse_nested_block(|p| {
   |  ______________________________^
14 | |             let parser = Temp(p);
15 | |             parse(&mut parser)
16 | |         })
   | |_________^
note: ...so that the expression is assignable
  --> src/main.rs:14:31
   |
14 |             let parser = Temp(p);
   |                               ^
   = note: expected `&mut Parser<'_, '_>`
              found `&mut Parser<'_, '_>`

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

Any advice about how to resolve this issue would be greatly appreciated!

It seems like the compiler is tripping up on the implied 'i: 'tt bound. This is the only way I've found to fix it:

use cssparser::{ParseError, Parser};

struct Temp<'p, 'i, 't>(&'p mut Parser<'i, 't>);

impl<'p, 'i, 't> Temp<'p, 'i, 't> {
    fn parse_nested_block<F, T, E>(&mut self, parse: F) -> Result<T, ParseError<'i, E>>
    where
        F: for<'q, 'tt> FnOnce(&mut Temp<'q, 'i, 'tt>) -> Result<T, ParseError<'i, E>>,
    {
        let p = &mut self.0;

        p.parse_nested_block(|p| {
            fn call<'i, F, T, E>(
                parse: F,
                parser: &mut Temp<'_, 'i, '_>,
            ) -> Result<T, ParseError<'i, E>>
            where
                F: FnOnce(&mut Temp<'_, 'i, '_>) -> Result<T, ParseError<'i, E>>,
            {
                parse(parser)
            }
            let mut parser = Temp(p);
            call(parse, &mut parser)
        })
    }
}

But it feels like there should really be a simpler solution here.

1 Like

That did the trick! Unfortunately the actual code is using a trait with associated types so I can't quite get this solution to work in the original code. I was already thinking about getting rid of the trait though so maybe I just need to bite the bullet on that.

Do you have any insight into what exactly about that wrapper function is fixing the error here?

Not really, no. Generally, though, putting all your values through function items with explicit lifetimes can help the compiler figure out what to do.

1 Like

After refactoring the trait out of the way I was able to get your fix to work for the actual implementation. Interestingly another method that previously didn't need a workaround did after refactoring?

Thanks for you help!

First of all, let's set up a Playground that reproduces the issue (you'll often get better help if you do this beforehand, since for edge cases in the compiler / borrow checker heuristics, there isn't always a theory that can let people help off the top of their heads, and instead, tinkering with a Playground is often necessary):

  • this yields the following Playground, but indeed, the higher-order 'tt lifetime parameter is needed to match Parser's API, so we amend it like you did, yielding the following:

  • Repro on Playground


Now, after some tinkering with it, I couldn't help but have a hunch that this higher-order inequality issue has some ressemblance with:

where, indeed, inference & higher-order closure signatures & chained inequalities with the higher-order lifetimes w.r.t. a fixed one confuse the borrow checker. Both in that issue, and in the solution of @LegionMammal978, the issue can be dodged by annotating the lifetimes / the signatures a bit more; in this instance through a whole funneler function which is indeed quite cumbersome.

And since the other issue can otherwise be simply solved by enabling a smarter borrow-checker (#![feature(nll)], to opt-into 100% NLL borrow-checker —it seems that we currently may only have 95% of it, of sorts)), I tried that approach with your code:

+ #![feature(nll)]

  use cssparser::…

and with it the code compiles fine.

So my own conclusion would be:

it's a compiler bug / limitation that will Soon™ be solved on stable Rust.

2 Likes

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.