a-b-street/abstreet · error

Couldn't read_dir

Error message

Couldn't read_dir {:?}: {}

What it means

list_dir reads a directory and returns sorted full paths; it silently treats a missing directory as empty (NotFound), but any other read_dir failure panics with this message. Such errors mean the path exists but can't be read: permission problems, the path is a file not a directory, or an OS-level I/O error.

Solutions

  1. Check the path is a directory (Path::is_dir) and readable before calling list_dir.
  2. Fix permissions on the directory (chmod/chown) or run with appropriate access.
  3. If a NotFound should be tolerated, note list_dir already returns an empty Vec for that case; handle other Err kinds by pre-checking the path.
  4. Reproduce the underlying io::Error from the panic message and address it (e.g. replace the file-vs-directory path, remount a failed volume).

Example fix

// before
let files = abstio::list_dir("data/input".to_string());

// after
let dir = std::path::Path::new("data/input");
let files = if dir.is_dir() {
    abstio::list_dir(dir.to_str().unwrap().to_string())
} else {
    eprintln!("data/input is not a readable directory");
    Vec::new()
};
Defensive patterns

Strategy: validation

Validate before calling

let dir = std::path::Path::new(&path);
if !dir.is_dir() {
    eprintln!("{:?} is not a directory (list_dir only tolerates NotFound)", dir);
}
if !dir.is_dir() || std::fs::read_dir(dir).is_err() {
    eprintln!("{:?} is not readable", dir);
}

Type guard

fn is_readable_dir(path: &str) -> bool {
    let p = std::path::Path::new(path);
    p.is_dir() && std::fs::read_dir(p).is_ok()
}

Try / catch

if !is_readable_dir(&dir_path) {
    eprintln!("{:?} is not a readable directory", dir_path);
    return Vec::new();
}
let files = abstio::list_dir(dir_path);

Prevention

When it happens

Trigger: Calling list_dir(path) where the path is a regular file rather than a directory; the process lacks read permission on the directory; an OS I/O error occurs while iterating entries; also entry.unwrap() inside the loop panics on a bad directory entry (e.g. invalid UTF-8 path).

Common situations: Passing a file path where a directory was expected; sandboxed/containerized environments without permission on the data directory; scanning directories with entries whose names aren't valid UTF-8; NFS/network mounts returning transient I/O errors.

Related errors


AI-assisted analysis of a-b-street/abstreet@0964f29315 (2026-09-13). Data as JSON: /api/errors/89c566176dee6dad. Report an issue: GitHub.

Appendix: source

Thrown at abstio/src/io_native.rs:31

use abstutil::{elapsed_seconds, prettyprint_usize, to_json, Timer, PROGRESS_FREQUENCY_SECONDS};

pub use crate::io::*;

pub fn file_exists<I: AsRef<str>>(path: I) -> bool {
    Path::new(path.as_ref()).exists()
}

/// Returns full paths
pub fn list_dir(path: String) -> Vec<String> {
    let mut files: Vec<String> = Vec::new();
    match fs_err::read_dir(&path) {
        Ok(iter) => {
            for entry in iter {
                files.push(entry.unwrap().path().to_str().unwrap().to_string());
            }
        }
        Err(ref e) if e.kind() == std::io::ErrorKind::NotFound => {}
        Err(e) => panic!("Couldn't read_dir {:?}: {}", path, e),
    };
    files.sort();
    files
}

pub fn slurp_file<I: AsRef<str>>(path: I) -> Result<Vec<u8>> {
    inner_slurp_file(path.as_ref())
}
fn inner_slurp_file(path: &str) -> Result<Vec<u8>> {
    || -> Result<Vec<u8>> {
        let mut file = File::open(path)?;
        let mut buffer = Vec::new();
        file.read_to_end(&mut buffer)?;
        Ok(buffer)
    }()
    .with_context(|| path.to_string())
}

View on GitHub (pinned to 0964f29315)