S08: Ignored Result (let _ =, .ok();)
What it looks like
Section titled “What it looks like”use std::io::Write;
pub fn write_invoice(out: &mut impl Write, invoice_id: u64, total_cents: u64) { let _ = writeln!(out, "Invoice {invoice_id}"); let _ = writeln!(out, "Total: {total_cents} cents"); out.flush().ok();}Each writeln! and the flush return a Result. let _ = and .ok() throw it away. If the disk is full, the function returns normally and the caller thinks that the invoice exists.
Why AI agents produce it
Section titled “Why AI agents produce it”- The compiler warns about an unused
Result(“unusedResultthat must be used”).let _ =is the fastest way to stop the warning. - The function signature has no
Result, so?does not compile. The agent ignores the error instead of changing the signature.
Why it is a problem
Section titled “Why it is a problem”- Silent data loss. Writes fail for real reasons: a full disk, a closed socket, a lost network drive. The program continues as if the data were saved.
- Hard to debug. Nothing is logged, and there is no error to follow. The problem shows up later, far from its cause.
- It hides the intent. A reader cannot tell if the author ignored the error on purpose or by accident.
The better version
Section titled “The better version”Make the function return a Result and propagate each error with ?:
WorseEvery write error is discarded. The caller always sees success.
use std::io::Write;pub fn write_invoice(out: &mut impl Write, invoice_id: u64, total_cents: u64) {let _ = writeln!(out, "Invoice {invoice_id}");let _ = writeln!(out, "Total: {total_cents} cents");out.flush().ok();}BetterReturns
io::Result<()>. A failed write reaches the caller.use std::io::{self, Write};pub fn write_invoice(out: &mut impl Write,invoice_id: u64,total_cents: u64,) -> io::Result<()> {writeln!(out, "Invoice {invoice_id}")?;writeln!(out, "Total: {total_cents} cents")?;out.flush()}
The last line, out.flush(), has no ;, so its io::Result<()> is the return value. See Result, not exceptions.
When it is acceptable
Section titled “When it is acceptable”Sometimes an error really does not matter, for example when a best-effort clean-up fails. Then make the decision visible: handle the error explicitly, log it, and write a comment.
pub fn remove_temp_file(path: &Path) { // Best effort: the OS cleans the temp directory later. A failure // here does not change the result, so we ignore it on purpose. if let Err(err) = fs::remove_file(path) { eprintln!("warning: could not remove {}: {err}", path.display()); }}A bare let _ = ...; with a comment that says why is also acceptable. Without the comment, it is a question for the agent.
How to detect it
Section titled “How to detect it”- Search for
let _ =and.ok();(a statement that ends right after.ok()). Also look for.unwrap_or_default()and.unwrap_or(())on operations that can fail. - Clippy
let_underscore_must_useandunused_result_ok(both in the restriction group, off by default) flag these patterns. - The compiler lint
unused_must_use(on by default) catches aResultthat is not used at all.let _ =exists to silence it, so it does not catch this smell.
What to tell your agent
Section titled “What to tell your agent”In write_invoice, do not discard errors. Change the return type toio::Result<()> and use ? on each writeln!, and return out.flush(). Update thecallers to handle the error. If you intentionally ignore an error anywhere,log it and add a comment that says why.