简体   繁体   中英

"cannot infer an appropriate lifetime" when using a closure to return a reference to an enum variant's content

I have a function that accepts a reference to an enum, which I need to parse by matching the enum and reading its content. One of the variants of the enum (not in the simplified minimal working example below), may contain as value the type of the enum itself, therefore I may need to recursively call the same function to parse its value.

I would like to write a function that acts as a filter and returns an Option::Some containing a reference to the content of the enum variant, or None if the value must be discarded.

What follows is a minimal working (not-really compiling) example:

enum Data<'a> {
    Value(&'a String),
    Null,
}

fn main() {
    let s = String::new();
    let d = Data::Value(&s);

    let equal = |d: &Data| -> Option<&String> {
        if let Data::Value(s) = d {
            Some(s)
        } else {
            None
        }
    };

    parse(&d, equal);
    //parse(&d, equal_filter);
}

fn equal_filter<'a>(d: &'a Data) -> Option<&'a String> {
    if let Data::Value(s) = d {
        Some(s)
    } else {
        None
    }
}

fn parse<'a, F>(data: &Data<'a>, filter: F)
where
    F: Fn(&Data<'a>) -> Option<&'a String>,
{
    filter(data);
}

Playground .

I tried to compile the code by using a closure first, but in that case I get the error:

error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
  --> src/main.rs:11:33
   |
11 |         if let Data::Value(s) = d {
   |                                 ^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #2 defined on the body at 10:17...
  --> src/main.rs:10:17
   |
10 |       let equal = |d: &Data| -> Option<&String> {
   |  _________________^
11 | |         if let Data::Value(s) = d {
12 | |             Some(s)
13 | |         } else {
14 | |             None
15 | |         }
16 | |     };
   | |_____^
   = note: ...so that the types are compatible:
           expected &Data<'_>
              found &Data<'_>
note: but, the lifetime must be valid for the expression at 18:5...
  --> src/main.rs:18:5
   |
18 |     parse(&d, equal);
   |     ^^^^^
note: ...so that a type/lifetime parameter is in scope here
  --> src/main.rs:18:5
   |
18 |     parse(&d, equal);
   |     ^^^^^

So I tried with a function, but a got another error:

error[E0271]: type mismatch resolving `for<'r> <for<'a, 's> fn(&'a Data<'s>) -> std::option::Option<&'a std::string::String> {equal_filter} as std::ops::FnOnce<(&'r Data<'_>,)>>::Output == std::option::Option<&std::string::String>`
  --> src/main.rs:19:5
   |
19 |     parse(&d, equal_filter);
   |     ^^^^^ expected bound lifetime parameter, found concrete lifetime
   |
note: required by `parse`
  --> src/main.rs:30:1
   |
30 | / fn parse<'a, F>(data: &Data<'a>, filter: F)
31 | | where
32 | |     F: Fn(&Data<'a>) -> Option<&'a String>,
33 | | {
34 | |     filter(data);
35 | | }
   | |_^

I would prefer to solve the issue using the closure, but I don't know how to proceed even by using the function.

Ultimately, this is caused due to limitations in Rust's type inference . Specifically, if a closure is passed immediately to a function that uses it, the compiler can infer what the argument and return types are. Unfortunately, when it is stored in a variable before being used, the compiler does not perform the same level of inference.

Inline your closure and it works:

enum Data<'a> {
    Value(&'a String),
    Null,
}

fn main() {
    let s = String::new();
    let d = Data::Value(&s);

    parse(&d, |d| match d {
        Data::Value(s) => Some(s),
        _ => None,
    });
}

fn parse<'a, F>(data: &Data<'a>, filter: F)
where
    F: Fn(&Data<'a>) -> Option<&'a String>,
{
    filter(data);
}

However, I'd encourage you to instead create methods on the enum and participate in the idiomatic set of conversion functions :

enum Data<'a> {
    Value(&'a String),
    Null,
}

impl<'a> Data<'a> {
    fn as_value(&self) -> Option<&'a str> {
        match self {
            Data::Value(s) => Some(s),
            _ => None,
        }
    }
}

fn main() {
    let s = String::new();
    let d = Data::Value(&s);

    parse(&d, Data::as_value);
}

fn parse<'a, F>(data: &Data<'a>, filter: F)
where
    F: Fn(&Data<'a>) -> Option<&'a str>,
{
    filter(data);
}

Your function variant doesn't work because you've put the relevant lifetime in the wrong place:

// Wrong
fn equal_filter<'a>(d: &'a Data) -> Option<&'a String>
// Right
fn equal_filter<'a>(d: &Data<'a>) -> Option<&'a String>

Using either #[deny(elided_lifetimes_in_paths)] or #[deny(rust_2018_idioms)] will guide you to this:

error: hidden lifetime parameters in types are deprecated
  --> src/main.rs:12:22
   |
12 |     let equal = |d: &Data| -> Option<&String> {
   |                      ^^^^- help: indicate the anonymous lifetime: `<'_>`
   |
error: hidden lifetime parameters in types are deprecated
  --> src/main.rs:24:28
   |
24 | fn equal_filter<'a>(d: &'a Data) -> Option<&'a String> {
   |                            ^^^^- help: indicate the anonymous lifetime: `<'_>`

See also:

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