ooabc
ooabc

Reputation: 53

argument validation in clap v4

I am using crate clap v4。When I try to write something validating arguments against regex, I had some problem with lifetimes.

My code as following:

pub fn validator_regex(r: &'static str) -> ValueParser {
    ValueParser::from(move |s: &str| -> std::result::Result<&str, Error> {
        let reg = regex::Regex::new(r).unwrap();
        match reg.is_match(s) {
            true => Ok(s),
            false => Err(Error::from(format!("not matches {}", r))),
        }
    })
}

pub fn validator_identifier() -> ValueParser {
    validator_regex("^[-_0-9a-zA-Z]+$")
}

And compilation error:

error: lifetime may not live long enough
   --> main\./src\cmd\cmd_util.rs:440:21
    |
437 |     ValueParser::from(move |s: &str| -> std::result::Result<&str, Error> {
    |                                -                            - let's call the lifetime of this reference `'2`
    |                                |
    |                                let's call the lifetime of this reference `'1`
...
440 |             true => Ok(s),
    |                     ^^^^^ returning this value requires that `'1` must outlive `'2`

Could any help me on these two questions:

pub fn validator_regex(r: &'static str) -> impl Fn(&str) -> Result<&str, String> {
    move |s: &str| -> Result<&str, String> {
        let reg = regex::Regex::new(r).unwrap();
        match reg.is_match(s) {
            true => Ok(s),
            false => Err(format!("not match {}", r)),
        }
    }
}

Upvotes: 4

Views: 2732

Answers (1)

Chayim Friedman
Chayim Friedman

Reputation: 71350

Let's take a look at the From impl you're invoking:

impl<P> From<P> for ValueParser
where
    P: TypedValueParser + Send + Sync + 'static,

Ok, take a look at TypedValueParser:

impl<F, T, E> TypedValueParser for F
where
    F: Fn(&str) -> Result<T, E> + Clone + Send + Sync + 'static,
    E: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
    T: Send + Sync + Clone,

So, the signature is not Fn(&str) -> Result<&str, E>, it is <T> Fn(&str) -> Result<T, E>. Is it different? Can't we just put &str in the place of T?

It is very different. Let's annotate the lifetimes:

// Fn(&str) -> Result<&str, E>
for<'a> Fn(&'a str) -> Result<&'a str, E>

// <T> Fn(&str) -> Result<T, E>
<&'b str> for<'a> Fn(&'a str) -> Result<&'b str, E>

Where does 'b come from? It can't be HRTB, as it is declared in the impl header (T) and not the trait bound (Fn). Therefore, it must be a fixed lifetime. See the mismatch? The lifetimes I called 'a and 'b are what the compiler calls '1 and '2, respectively. If 'b is fixed, it cannot be derived from the dynamic HRTB 'a.

The fix is simple: just don't return &str, return String instead:

pub fn validator_regex(r: &'static str) -> ValueParser {
    ValueParser::from(move |s: &str| -> std::result::Result<String, Error> {
        let reg = regex::Regex::new(r).unwrap();
        match reg.is_match(s) {
            true => Ok(s.to_owned()),
            false => Err(Error::from(format!("not matches {}", r))),
        }
    })
}

Upvotes: 2

Related Questions