Lifetimes and clap validator

I'm migrating from clap v2.33 to clap v3.15 and stuck with the following lifetime error.

Original code (clap 2.33):

use clap::{App, Arg};
use core::fmt::Display;

fn val<T>(string: T) -> Result<(), String>
where
    T: AsRef<str> + Display,
{
    if string.as_ref().len() > 4 {
        return Ok(());
    } else {
        return Err("banana".to_string());
    }
}

fn is_validator<T>(string: T) -> Result<(), String>
where
    T: AsRef<str> + Display,
{
    val(string)
}

pub fn fee_payer_arg<'a, 'b>() -> Arg<'a, 'b> { // two lifetimes, but don't get where they come
    Arg::with_name("NAME")
        .long("name")
        .validator(is_validator)
}

New code (v3.15)

use clap::{Command, Arg};
use core::fmt::Display;

fn val<T>(string: T) -> Result<(), String>
where
    T: AsRef<str> + Display,
{
    if string.as_ref().len() > 4 {
        return Ok(());
    } else {
        return Err("banana".to_string());
    }
}

fn is_validator<T>(string: T) -> Result<(), String>
where
    T: AsRef<str> + Display,
{
    val(string)
}

pub fn fee_payer_arg<'a>() -> Arg<'a> {
    Arg::new("NAME")
        .long("name")
        .validator(is_validator) // error here
}

The error is quite long:

error[E0308]: mismatched types
    --> src/main.rs:62:10
     |
62   |         .validator(is_validator)
     |          ^^^^^^^^^ lifetime mismatch
     |
     = note: expected type `<fn(&str) -> Result<(), std::string::String> {is_validator::<&str>} as FnOnce<(&str,)>>`
                found type `<fn(&str) -> Result<(), std::string::String> {is_validator::<&str>} as FnOnce<(&str,)>>`
note: the required lifetime does not necessarily outlive the lifetime `'a` as defined here
    --> src/main.rs:59:22
     |
59   | pub fn fee_payer_arg<'a>() -> Arg<'a> {
     |                      ^^
note: the lifetime requirement is introduced here
    --> /home/klykov/.cargo/registry/src/github.com-1ecc6299db9ec823/clap-3.1.6/src/build/arg.rs:1535:27
     |
1535 |         F: FnMut(&str) -> Result<O, E> + Send + 'help,
     |                           ^^^^^^^^^^^^

For more information about this error, try `rustc --explain E0308`.
error: could not compile `args` due to previous error

For the new code, Rust can't or is failing to turn the type parameter T on is_validator into something higher-ranked; that is, this fails

    let _: fn(&str) -> Result<(), String> = is_validator;

Because T can only represent one type, but we're trying to get it to represent &'x str for any lifetime 'x.

You can fix it by wrapping the call in a closure:

pub fn fee_payer_arg<'a>() -> Arg<'a> {
    Arg::new("NAME")
        .long("name")
        .validator(|s| is_validator(s))
}

Conceptually, a different T is chosen for each lifetime called on the closure. (Don't worry, it all monomorphizes down to one function for all &str.)

I didn't play with the old code.

could you suggest something to read to have a better understanding on the lifetimes? I guess here "lifetime elision" fails, so would be good to know more

I don't have a good citation to learn about this particular area (the interaction of generics and higher-ranked types or bounds), I'm afraid. It's an under-documented part of the language.

Do note that validator has a bound of:

F: FnMut(&str) -> Result<O, E> + Send + 'help,

Which is sugar for:

for<'any> F: FnMut(&'any str) -> Result<O, E> + Send + 'help,

I.e. a higher-ranked bound as discussed in the Nomicon.

This comment tersely discusses the core difficulty here -- a type parameter T can only resolve to a single type, and that's not general enough for your fn<T>(T) -> ... to resolve to a fn(&str) -> ... (aka for<'any> fn(&'any str) -> ...).

This is true even though the function can resolve to fn(&'x str) -> ... for any concrete 'x. Given a concrete 'x, &'x str is a single type.

Maybe someday it could be coercible to the higher-ranked type or otherwise implement the higher-ranked bound -- the closure workaround shows it can fulfill the core functionality somewhat trivially -- but I know if no concrete proposals for the compiler to support that implicitly, either. The exact relationship between higher-ranked and non-higher-ranked generic types is not yet set in stone, and being too implicit may be a future foot-gun, depending on how things go.

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.