a-b-street/abstreet · error

Couldn't read_object

Error message

Couldn't read_object({}): {}

What it means

must_read_object loads a file that may be JSON or bincode (dispatched by the .bin extension via read_object) and panics with this message if reading or deserialization fails. It is the infallible wrapper around read_object, which returns Result. The panic indicates the path is missing/unreadable, or the contents don't parse into T.

Solutions

  1. Confirm the file exists at the exact path (abstio::file_exists) and has the right extension (.json/.geojson or .bin).
  2. Validate the file's JSON parses (e.g. serde_json::from_str in isolation) or regenerate the .bin file.
  3. Use read_object and match on the Err to recover or skip the file, as load_all_objects does.
  4. If types changed between versions, migrate or re-export the input files with the current schema.

Example fix

// before
let obj = abstio::must_read_object::<Config>(path.clone(), &mut timer);

// after
let obj = match abstio::read_object::<Config>(path.clone(), &mut timer) {
    Ok(o) => o,
    Err(err) => {
        eprintln!("Couldn't read {}: {}", path, err);
        Config::default()
    }
};
Defensive patterns

Strategy: fallback

Validate before calling

if !abstio::file_exists(&path)
    || !(path.ends_with(".json") || path.ends_with(".geojson") || path.ends_with(".bin")) {
    eprintln!("{} missing or has wrong extension", path);
}

Type guard

fn is_readable_object_path(path: &str) -> bool {
    let p = std::path::Path::new(path);
    p.is_file()
        && (path.ends_with(".json") || path.ends_with(".geojson") || path.ends_with(".bin"))
}

Try / catch

match abstio::read_object::<T>(path.clone(), &mut timer) {
    Ok(obj) => obj,
    Err(err) => { eprintln!("read_object({}) failed: {}", path, err); Default::default() }
}

Prevention

When it happens

Trigger: Calling must_read_object(path, timer) with a nonexistent or unreadable path; a JSON file with invalid JSON; a .bin file with invalid/mismatched bincode data; a path lacking .json/.geojson (read_json bails) or malformed contents; type T not matching the file's schema.

Common situations: Typos in data paths or wrong working directory; input datasets edited by hand and broken; schema drift after upgrading the crate's types so old JSON/bincode files no longer deserialize; forgetting the .json extension so maybe_read_json bails.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


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

Appendix: source

Thrown at abstio/src/io.rs:50

        Ok(obj) => obj,
        Err(err) => panic!("Couldn't read_binary({}): {}", path, err),
    }
}

/// May be a JSON or binary file
pub fn read_object<T: DeserializeOwned>(path: String, timer: &mut Timer) -> Result<T> {
    if path.ends_with(".bin") {
        maybe_read_binary(path, timer)
    } else {
        maybe_read_json(path, timer)
    }
}

/// May be a JSON or binary file. Panics on failure.
pub fn must_read_object<T: DeserializeOwned>(path: String, timer: &mut Timer) -> T {
    match read_object(path.clone(), timer) {
        Ok(obj) => obj,
        Err(err) => panic!("Couldn't read_object({}): {}", path, err),
    }
}

/// Keeps file extensions
pub fn find_prev_file(orig: String) -> Option<String> {
    let mut files = list_dir(parent_path(&orig));
    files.reverse();
    files.into_iter().find(|f| *f < orig)
}

pub fn find_next_file(orig: String) -> Option<String> {
    let files = list_dir(parent_path(&orig));
    files.into_iter().find(|f| *f > orig)
}

/// Load all serialized things from a directory, return sorted by name, with file extension removed.
/// Detects JSON or binary. Filters out broken files.
pub fn load_all_objects<T: DeserializeOwned>(dir: String) -> Vec<(String, T)> {

View on GitHub (pinned to 0964f29315)