Reputation: 156
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
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
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
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