James Durand
James Durand

Reputation: 156

Assign value from match statement

I'm trying to make a Git command in Rust. I'm using the clap argument parser crate to do the command line handling. I want my command to take an optional argument for which directory to do work in. If the command does not receive the option it assumes the users home directory.

I know that I can use the std::env::home_dir function to get the user's home directory if it is set but the part that confuses me is how to properly use the match operator to get the value of the path. Here is what I've been trying:

use std::env;

fn main() {
    // Do some argument parsing stuff...

    let some_dir = if matches.is_present("some_dir") {
        matches.value_of("some_dir").unwrap()
    } else {
        match env::home_dir() {
            Some(path) => path.to_str(),
            None       => panic!("Uh, oh!"),
        }
    };

    // Do more things

I get an error message when I try to compile this saying that path.to_str() doesn't live long enough. I get that the value returned from to_str lives for the length of the match scope but how can you return a value from a match statement that has to call another function?

Upvotes: 0

Views: 989

Answers (3)

Matthieu M.
Matthieu M.

Reputation: 299880

There is a rather simple trick: increase the scope of path (and thus its lifetime) so that you can take a reference into it.

use std::env;

fn main() {
    // Do some argument parsing stuff...

    let path; // <--

    let some_dir = if matches.is_present("some_dir") {
        matches.value_of("some_dir").unwrap()
    } else {
        match env::home_dir() {
            Some(p)    => { path = p; path.to_str().unwrap() },
            None       => panic!("Uh, oh!"),
        }
    };

    // Do more things
}

It is efficient, as path is only ever used when necessary, and does not require changing the types in the program.

Note: I added an .unwrap() after .to_str() because .to_str() returns an Option. And do note that the reason it returns an Option<&str> is because not all paths are valid UTF-8 sequences. You might want to stick to Path/PathBuf.

Upvotes: 2

mcarton
mcarton

Reputation: 30021

path.to_str() will return a &str reference to the inner string contained in path, which will only live as long as path, that is inside the match arm. You can use to_owned to get an owned copy of that &str. You will have to adapt the value from clap accordingly to have the same types in both branches of your if:

let some_dir = if matches.is_present("some_dir") {
    matches.value_of("some_dir").unwrap().to_owned()
} else {
    match env::home_dir() {
        Some(path) => path.to_str().unwrap().to_owned(),
        None       => panic!("Uh, oh!"),
    }
};

Alternatively, you could use Cow to avoid the copy in the first branch:

use std::borrow::Cow;

let some_dir: Cow<str> = if matches.is_present("some_dir") {
    matches.value_of("some_dir").unwrap().into()
} else {
    match env::home_dir() {
        Some(path) => path.to_str().unwrap().to_owned().into(),
        None       => panic!("Uh, oh!"),
    }
};

Upvotes: 2

A.B.
A.B.

Reputation: 16640

What is happening is that the scope of the match statement takes ownership of the PathBuf object returned from env::home_dir(). You then attempt to return a reference to that object, but the object ceases to exist immediately.

The solution is to return PathBuf rather than a reference to it (or convert it to a String and return that instead, in any case, it has to be some type that owns the data). You may have to change what matches.value_of("some_dir").unwrap() returns so that both branches return the same type.

Upvotes: 1

Related Questions