otm
otm

Reputation: 745

How do I use a value after calling Option::map?

I am trying to:

  1. Get an Option<&str> from somewhere, and build a PathBuf from it.
  2. If None, print some message, and return.
  3. If the path is not a directory, print a message saying the path is not a directory, and return.
  4. If everything is good, continue the program.
use std::path::PathBuf;

fn it_works() {
    let path_str = Some("/tmp/abc");
    let path = path_str.map(|s| PathBuf::from(s));
    if !path.map_or(false, |p| p.is_dir()) {
        match path {
            Some(p) => println!("The folder {:?} is not a directory!", p),
            None => println!("The repository folder is not set!"),
        }
        return;
    }
}

The pattern matching in the above snippet doesn't work because the value has been moved in the map_or combinator:

error[E0382]: use of moved value
 --> src/lib.rs:8:18
  |
5 |     let path = path_str.map(|s| PathBuf::from(s));
  |         ---- move occurs because `path` has type `std::option::Option<std::path::PathBuf>`, which does not implement the `Copy` trait
6 |     if !path.map_or(false, |p| p.is_dir()) {
  |         ---- value moved here
7 |         match path {
8 |             Some(p) => println!("The folder {:?} is not a directory!", p),
  |                  ^ value used here after move

I can do something like this, but it doesn't feel very "idiomatic" because of the unwrap and multiple if clauses:

let path_str = Some("/tmp/abc");
let path = path_str.map(|s| PathBuf::from(s));
if path.is_none() {
    println!("The repository folder is not set!");
    return;
}
let p = path.unwrap();
if !p.is_dir() {
    println!("The folder {:?} is not a directory!", p);
}

Could there be a better to solve this?

Upvotes: 0

Views: 1296

Answers (2)

Laney
Laney

Reputation: 1649

PathBuf implements FromStr trait, so you can use it, in combination with powerful pattern matching.

fn it_works() {
    use std::path::*;
    use std::str::FromStr;

    let path_str: Option<&str> = Some("/tmp/abc");
    match path_str.map(PathBuf::from_str) {
        Some(Ok(p)) => if !p.is_dir() {},
        Some(Err(e)) => {}
        None => {}
    };
}

Upvotes: 1

Freyja
Freyja

Reputation: 40794

If the closure in .map(...) (or any similar functions on an Option) doesn't need ownership of the value in the option (i.e. it only needs a reference to the value), you can always use option.as_ref() or option.as_mut() to turn an &Option<T> or &mut Option<T> into an Option<&T> or Option<&mut T>. Then calling .map() will not take ownership because references are copyable, so it's just copied into the provided closure.

With this in mind, your code would be modified to be this:

fn it_works() {
    let path_str = Some("/tmp/abc");
    let path = path_str.map(|s| PathBuf::from(s));
    if !path.as_ref().map_or(false, |p| p.is_dir()) {
        //  ^^^^^^^^^ using .as_ref() here
        //                          ^^^ now p is a '&PathBuf' instead of 'PathBuf'

        match path {
        //    ^^^^ we didn't take ownership so compiler doesn't complain here

            Some(p) => println!("The folder {:?} is not a directory!", p),
            None => println!("The repository folder is not set!"),
        }
        return;
    }
}

Upvotes: 2

Related Questions