0
votes

I'm using Rust 1.15 nightly and have some code with an iterator over std::io::Result<std::fs::DirEntry> named dir_contents_iterator that I'm trying to partition based on whether each DirEntry corresponds to a file or a folder (with Err values sorted into the file list):

let (dir_list, file_list): (Vec<std::io::Result<DirEntry>>, Vec<std::io::Result<DirEntry>>) =
    dir_contents_iterator.partition(|entry_result| {
        match entry_result {
            Ok(entry) => {
                entry
                    .file_type()
                    .and_then(|file_type| Ok(file_type.is_dir()))
                    .unwrap_or(false)
            },
            Err(_) => false
        }
    });

This code fails to compile with the following two errors:

error[E0308]: mismatched types
   --> src/main.rs:179:13
    |
179 |             Ok(entry) => {
    |             ^^^^^^^^^ expected reference, found enum `std::result::Result`
    |
    = note: expected type `&std::result::Result<std::fs::DirEntry, std::io::Error>`
    = note:    found type `std::result::Result<_, _>`

error[E0308]: mismatched types
   --> src/main.rs:185:13
    |
185 |             Err(_) => false
    |             ^^^^^^ expected reference, found enum `std::result::Result`
    |
    = note: expected type `&std::result::Result<std::fs::DirEntry, std::io::Error>`
    = note:    found type `std::result::Result<_, _>`

I ended up solving my problem by rewriting my code to partition out the Err values first before partitioning between files and folders. This allows the match to be removed from the shown code and now it compiles. But for the sake of understanding, I'd still like to know why my original match's branch arms expect an &Result reference rather than accepting a Result, especially in the case of the second branch arm (Err(_) => false) which doesn't even use the value!

Here's the problem code in the context of the project I'm working on in case I've left out any details that help explain the problem.

1

1 Answers

1
votes

Using this as a MCVE (which you should have provided):

use std::fs::{self, DirEntry};

fn main() {
    let dirs = fs::read_dir("/tmp").expect("Nope");

    let (dir_list, file_list): (Vec<std::io::Result<DirEntry>>, Vec<std::io::Result<DirEntry>>) =
        dirs.partition(|entry_result| {
            match entry_result {
                Ok(entry) => {
                    entry.file_type()
                        .and_then(|file_type| Ok(file_type.is_dir()))
                        .unwrap_or(false)
                }
                Err(_) => false,
            }
        });
}

You can then print out the type of the variable

dirs.partition(|entry_result| {
    let () = entry_result;
    false
});

Which shows that entry_result is a &std::result::Result<std::fs::DirEntry, std::io::Error>.

So the answer to

why my original match's branch arms expect an &Result reference

is because you are giving it a &Result!


For completeness, you can dereference the variable and then reference the components:

match *entry_result {
    Ok(ref entry) => {

This is redundant:

.and_then(|file_type| Ok(file_type.is_dir()))

as it can just be map:

.map(|m| m.is_dir())

And some more cleanup:

use std::fs;

fn main() {
    let dirs = fs::read_dir("/tmp").expect("Nope");

    let (dir_list, file_list): (Vec<_>, Vec<_>) =
        dirs.partition(|entry_result| {
            entry_result.as_ref()
                .ok()
                .and_then(|entry| entry.file_type().ok())
                .map(|ft| ft.is_dir())
                .unwrap_or(false)
        });
}

I was under the impression that * and ref would just undo each other.

* and ref might undo each other, but that would only apply if they were applied to the same value. Here, we are pattern matching, so we take the &Result and pattern-match against the dereferenced value. We then take a reference to the value inside the Result, so nothing is ever moved, but we aren't dereferencing and re-referencing the same thing.