nbari
nbari

Reputation: 26965

clap - how to pass a default_value when returning ArgMatches<'static>

To reduce lines of code I moved my clap App to another file with something like this:

playground

use clap::{App, AppSettings, Arg, ArgMatches}; // 2.33.3
use std::path::Path;

fn main() {
    let s3m_dir = Path::new("/tmp").join(".s3m");

    let matches = get_matches(s3m_dir.display().to_string());
    println!("{:#?}", matches);
}

pub fn get_matches(home_dir: String) -> ArgMatches<'static> {
    App::new("s3m")
        .version(env!("CARGO_PKG_VERSION"))
        .setting(AppSettings::SubcommandsNegateReqs)
        .after_help(format!("foo bar: {}", home_dir).as_ref())
        .arg(
            Arg::with_name("config")
                .help("config.yml")
                .long("config")
                .short("c")
                .default_value(&format!("{}/.s3m/config.yml", home_dir))
                .required(true)
                .value_name("config.yml"),
        )
        .get_matches()
}

The problem I have is that I don't know how could I use the argument home_dir as the default_value, here:

.default_value(&format!("{}/.s3m/config.yml", home_dir))

The signature for default_value is:

pub fn default_value(self, val: &'a str) -> Self

How could I pass a format!("{}/.s3m/config.yml", home_dir with a lifetime in other to satisfy the signature?

Upvotes: 2

Views: 1363

Answers (1)

Kevin Reid
Kevin Reid

Reputation: 43852

I haven't used clap, so there may be a better approach, but the general Rust solution to this problem is to have some data structure that owns the needed strings, so that the ArgMatches can have a lifetime dependent on it:

struct ArgParser {
    home_dir: PathBuf,
    default_config: OsString,
}

impl ArgParser {
    pub fn new(home_dir: &Path) -> Self {
        let default_config_path: PathBuf = home_dir.join(".s3m/config.yml");
        Self {
            home_dir: home_dir.to_owned(),
            default_config: default_config_path.as_os_str().to_owned(),
        }
    }

I've also adjusted the config path to use Path::join rather than string formatting and OsString instead of String, which aren't actually relevant to your question but should be more correct.

Now we can modify get_matches to work with this, as part of impl ArgParser:


    pub fn get_matches(&self) -> ArgMatches {
        App::new("s3m")
            .version(env!("CARGO_PKG_VERSION"))
            .setting(AppSettings::SubcommandsNegateReqs)
            .after_help(format!("foo bar: {}", self.home_dir.display()).as_ref())
            .arg(
                Arg::with_name("config")
                    .help("config.yml")
                    .long("config")
                    .short("c")
                    .default_value_os(&self.default_config)
                    .required(true)
                    .value_name("config.yml"),
            )
            .get_matches()
    }
}

Notice that there is no lifetime parameter given for ArgMatches. This is because the compiler will automatically infer the lifetime for us, as if we had written:

pub fn get_matches<'a>(&'a self) -> ArgMatches<'a> {...}

The lifetime is no longer 'static, but it can't be 'static (unless you choose to leak the strings that you're configuring App with). Instead, if you you need a string to live longer than the ArgParser, use .to_owned() to convert &'a str into a String that can live independently.

playground

Upvotes: 5

Related Questions