I have a function that reads a configuration file. It receives as a parameter a Option<String>
that contains the path to the configuration file if the user optionally sets it as a command line argument. If not defined the function computes a default path using the user home directory (home
crate) concatenated with a default file name. This code works but the match
looks a bit like there must be some more idiomatic way in Rust to resolve this. I can not use unwrap_or()
because it doesn't allow multiple statements needed to compute the default path.
fn get_config(config_file: Option<String>) -> Result<Config, Box<dyn std::error::Error>> {
let credentials_path = match config_file {
None => {
let mut default_path = (home::home_dir()).ok_or_else(||{"Can not find home directory"})?;
default_path.push("credentials.toml");
default_path
},
Some(s) => s.into(),
};
println!("path: {:?}", credentials_path);
let contents = std::fs::read_to_string(credentials_path)?;
let config: Config = toml::from_str(contents.as_str()).unwrap();
Ok(config)
}
Is there some other feature that could be used to implement this code in a more rusty way?
I can not use
unwrap_or()
because it doesn't allow multiple statements needed to compute the default path.
You can use blocks in any expression in Rust (and similarly, the body of a closure is any expression, not necessarily a block):
let credentials_path = config_file.map(|p| p.into()).unwrap_or({
let mut default_path = home::home_dir().ok_or_else(|| "Cannot find home directory")?;
default_path.push("credentials.toml");
default_path
});
But it’s preferable not to evaluate this block if its value isn’t required (i.e. to use unwrap_or_else
), and the combination of that, the .map
into
, and the ?
operator that returns from the outer function make match
a relatively clean option here compared to this kind of pattern:
let credentials_path = config_file.map(|p| Ok(p.into())).unwrap_or_else(|| {
let mut default_path = home::home_dir().ok_or("Cannot find home directory")?;
default_path.push("credentials.toml");
Ok(default_path)
})?;
(which might even need more type annotations.)
So in the end, I’d go with:
let credentials_path = match config_file {
None => {
let mut default_path = home::home_dir().ok_or("Cannot find home directory")?;
default_path.push("credentials.toml");
default_path
}
Some(s) => s.into(),
};
println!("path: {:?}", credentials_path);
let contents = std::fs::read_to_string(credentials_path)?;
toml::from_str(&contents).into()