1

I'm a Java developer and in the process of learning Rust. Here is a tiny example that reads the file names of the current directory into a string list/vector and then outputs it.

Java with nio:

import java.io.*;
import java.nio.file.*;
import java.util.*;
import java.util.stream.*;
    
public class ListFileNames {
    
    public static void main(String[] args) {
        final Path dir = Paths.get(".");
        final List<String> fileNames = new ArrayList<>();
        try {
            final Stream<Path> dirEntries = Files.list(dir);
            dirEntries.forEach(path -> {
                if (Files.isRegularFile(path)) {
                        fileNames.add(path.getFileName().toString());
                }
            });
        }
        catch (IOException ex) {
            System.err.println("Failed to read " + dir + ": " + ex.getMessage());
        }
    
        print(fileNames);
    }
    
    private static void print(List<String> names) {
        for (String name : names) {
            System.out.println(name);
        }
    }
}

Here is what I came up with in Rust:

use std::fs;
use std::path::Path;

fn main() {
    let dir = Path::new(".");
    let mut entries: Vec<String> = Vec::new();
    let dir_name = dir.to_str().unwrap();
    let dir_content = fs::read_dir(dir);
    match dir_content {
        Ok(dir_content) => {
            for entry in dir_content {
                if let Ok(dir_entry) = entry {
                    if let Ok(file_type) = dir_entry.file_type() {
                        if file_type.is_file() {
                            if let Some(string) = dir_entry.file_name().to_str() {
                                entries.push(String::from(string));
                            }
                        }
                    }
                }
            }
        }
        Err(error) => println!("failed to read {}: {}", dir_name, error)
    }

    print(entries);
}

fn print(names: Vec<String>) {
    for name in &names {
        println!("{}", name);
    }
}

Are there means to reduce the excessive indentations in the Rust code making it more easier to read similar to the Java code?

Jmb
  • 18,893
  • 2
  • 28
  • 55
Thomas S.
  • 5,804
  • 5
  • 37
  • 72

3 Answers3

6

It is possible to use ? to short-circuit execution, as demonstrated by @Bazaim, although this has slightly different semantics as it stops on the first error, rather than ignoring it.

To keep true to your semantics, you would move to a Stream-based Iterator-based approach as can be seen here:

use std::error::Error;
use std::fs;
use std::path::Path;

fn main() -> Result<(), Box<dyn Error>> {
    let dir = Path::new(".");
    let dir_content = fs::read_dir(dir)?;

    dir_content.into_iter()
        .filter_map(|entry| entry.ok())
        .filter(|entry| if let Ok(t) = entry.file_type() { t.is_file() } else { false })
        .filter_map(|entry| entry.file_name().into_string().ok())
        .for_each(|file_name| println!("{}", file_name));

    Ok(())
}

I am not quite happy with that filter step, but I do like the fact that the functional-style API cleanly separates each step.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
4

I'm also a beginner at Rust.
The ? operator is very usefull.
Here is the smallest version I get :

use std::error::Error;
use std::fs;
use std::path::Path;

fn main() -> Result<(), Box<dyn Error>> {
    let dir = Path::new(".");
    let mut entries: Vec<String> = Vec::new();
    let dir_content = fs::read_dir(dir)?;

    
    for entry in dir_content {
        let entry = entry?;
        if entry.file_type()?.is_file() {
            if let Some(string) = entry.file_name().to_str() {
                entries.push(String::from(string));
            }
        }
    }

    print(entries);

    Ok(())
}

fn print(names: Vec<String>) {
    for name in &names {
        println!("{}", name);
    }
}
Obzi
  • 2,384
  • 1
  • 9
  • 22
  • Is it possible to 'catch' the Errors somehow, so already collected file names are printed while on the 10th an error occurs? – Thomas S. Jul 24 '20 at 11:32
  • Since the `?` is pretty much a `throw` and `Try / Catch` doen't exists, we can't do anything with the `Err` in my code. Instead Matthieu M. code will do the job right. – Obzi Jul 24 '20 at 11:39
  • @ThomasS.: You don't need to catch to print _until_ the error: you just need to swap `entries.push(String::from(string))` with `println!("{}", string)`. It's only if you want to continue _past_ the error that you need to refactor the code; for example, extracting the loop body into a `fn get_file_name(entry: &io::Result) -> Result>` would allow to use the short-circuiting `?` within `get_file_name` whilst being able to match on the `Result` in the loop itself :) – Matthieu M. Jul 24 '20 at 13:27
3

Slightly modified version of @Bazaim's answer.

So, you can write the fallible code in a separate function and pass it reference to the vector.

If it fails, you can print the error and still print the names from the vector you passed in.

use std::error::Error;
use std::fs;
use std::path::Path;

fn main() {
    let mut names = vec![];
    if let Err(e) = collect(&mut names) {
        println!("Error: {}", e);
    }
    for name in names {
        println!("{}", name);
    }
}

fn collect(names: &mut Vec<String>) -> Result<(), Box<dyn Error>>  {
    let dir = Path::new(".");
    let dir_content = fs::read_dir(dir)?;

    for entry in dir_content {
        let entry = entry?;
        if entry.file_type()?.is_file() {
            if let Some(string) = entry.file_name().to_str() {
                names.push(String::from(string));
            }
        }
    }

    Ok(())
}
Gurwinder Singh
  • 38,557
  • 6
  • 51
  • 76