a-b-street/abstreet · warning · anyhow::Error
no file chosen to save
Error message
no file chosen to save
What it means
In map_gui's FileSaver::new_state, the native (non-wasm) branch shows a save-file dialog; if the user cancels or selects nothing, the builder returns no path and the code errs with anyhow!("no file chosen to save"). This distinguishes user cancellation from a genuine write failure.
Solutions
- Treat the Err as expected user cancellation and skip any error UI/teardown
- Pre-supply an output path via CLI flags to skip the dialog
- Catch the error and log at info level instead of surfacing it
Example fix
// before
FileSaver::new(ctx, "Save edits", contents, "json").consume();
// after
if let Err(err) = FileSaver::new(ctx, "Save edits", contents, "json").consume() {
log::info!("save cancelled: {}", err);
} Defensive patterns
Strategy: try-catch
Validate before calling
// confirm a path before attempting the native save flow
if output_path.is_none() && !interactive_dialogs_supported() {
return; // skip GUI save
} Try / catch
match FileSaver::new(ctx, "Save edits", contents, "json").consume() {
Ok(_) => {},
Err(e) if e.to_string().contains("no file chosen") => { /* user cancelled; ignore */ }
Err(e) => ctx.toast_error(format!("Save failed: {}", e)),
} Prevention
- Treat dialog cancellation as a normal outcome, not an error
- Offer CLI/config-supplied output paths for automation
- Don't wrap the result in error toasts without filtering cancellations
When it happens
Trigger: Invoking a save operation (e.g. saving map edits, metrics, or a PNG) on native and dismissing/cancelling the native file-save dialog.
Common situations: Users pressing Escape or Cancel in the save dialog; automated tests that can't interact with the dialog; headless runs of the GUI.
Related errors
AI-assisted analysis of a-b-street/abstreet@0964f29315 (2026-09-13).
Data as JSON: /api/errors/4e69d7456c1df83e.
Report an issue: GitHub.
Appendix: source
Thrown at map_gui/src/tools/ui.rs:94
ctx,
Box::pin(async move {
let mut builder = rfd::AsyncFileDialog::new().set_file_name(&filename);
if let Some(dir) = start_dir {
builder = builder.set_directory(&dir);
}
#[cfg(not(target_arch = "wasm32"))]
let result = if let Some(handle) = builder.save_file().await {
let path = handle.path().display().to_string();
// Both cases do AsRef<[u8]>
match write {
FileSaverContents::String(string) => fs_err::write(&path, string),
FileSaverContents::Bytes(bytes) => fs_err::write(&path, bytes),
}
.map(|_| path)
.map_err(|err| err.into())
} else {
Err(anyhow!("no file chosen to save"))
};
#[cfg(target_arch = "wasm32")]
let result = {
// Hide an unused warning
let _ = builder;
// TODO No file save dialog on WASM until
// https://developer.mozilla.org/en-US/docs/Web/API/Window/showSaveFilePicker
match write {
FileSaverContents::String(string) => {
abstio::write_file(filename.clone(), string)
}
FileSaverContents::Bytes(_bytes) => {
// We need to use write_file (which downloads a file), but encode
// binary data the right way
Err(anyhow!("writing binary files on web unsupported"))
}View on GitHub (pinned to 0964f29315)