a-b-street/abstreet · warning

Not saving

Error message

Not saving {}

What it means

abstio::write_raw on web only persists paths under the player data directory (localStorage); writing anywhere else is refused with "Not saving {path}". This is intentional: web builds cannot write to the real filesystem, so arbitrary paths are rejected rather than silently dropped.

Solutions

  1. On web, write only to paths under the player data directory.
  2. For user-facing exports, use the download-style API (write_file) which triggers a browser download instead.
  3. Gate saving code paths with cfg(not(target_arch = "wasm32")) or check the target before attempting the write.

Example fix

// before
abstio::write_binary("../data/player/settings.bin", &settings);
// after
#[cfg(target_arch = "wasm32")]
abstio::write_file("settings.bin", abstutil::to_binary(&settings));
#[cfg(not(target_arch = "wasm32"))]
abstio::write_binary("../data/player/settings.bin", &settings);
Defensive patterns

Strategy: validation

Validate before calling

fn can_persist_on_web(path: &str) -> bool { path.starts_with("../data/player/") }

Try / catch

if abstio::write_raw(path, bytes).is_err() {
    log::warn!("web build cannot save {} - skipping", path);
}

Prevention

When it happens

Trigger: Calling write_raw / write_binary with a path outside data/player/ on the web target — e.g. saving maps, settings, or recordings to data/system or other native-only locations.

Common situations: Porting native code that writes arbitrary output files to web; attempting to export large files expecting a real download; assuming symmetric read/write support on web.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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

Appendix: source

Thrown at abstio/src/io_web.rs:137

    // Only save for data/player, for now
    if !path.starts_with(&path_player("")) {
        warn!("Not saving {}", path);
        return;
    }

    let window = web_sys::window().unwrap();
    let storage = window.local_storage().unwrap().unwrap();
    storage.set_item(&path, &abstutil::to_json(obj)).unwrap();
}

pub fn write_binary<T: Serialize>(path: String, obj: &T) {
    write_raw(path, &abstutil::to_binary(obj)).unwrap();
}

pub fn write_raw(path: String, bytes: &[u8]) -> Result<()> {
    // Only save for data/player, for now
    if !path.starts_with(&path_player("")) {
        bail!("Not saving {}", path);
    }

    let window = web_sys::window().unwrap();
    let storage = window.local_storage().unwrap().unwrap();
    // Local storage only supports strings, so base64 encoding needed
    use base64::Engine;
    let encoded = base64::engine::general_purpose::STANDARD.encode(bytes);
    storage.set_item(&path, &encoded).map_err(|err| {
        anyhow!(err.as_string().unwrap_or_else(|| format!(
            "set_item for {path} failed for encoded length {}",
            encoded.len()
        )))
    })?;
    Ok(())
}

pub fn delete_file<I: AsRef<str>>(path: I) {
    let path = path.as_ref();

View on GitHub (pinned to 0964f29315)