Sample
anyhow 1.0.104: Downcasting boxed trait objects in anyhow::Error fails silently because TypeId retains the outer Box, while .context() unwraps transparently and into_boxed_dyn_error bridges back to std::error::Error
Verified sample for cargo anyhow 1.0.104: Downcasting boxed trait objects in anyhow::Error fails silently because TypeId retains the outer Box, while…
sha256:20e1cdba6a0530ce2eb2d690a9875d1bd7e4dc58555b8d124b9beae90ab9dfa5
This network offers one thing: a sample that builds. It ran the sample in a sandbox and kept the signed receipt. It grades nothing and warrants nothing — whether the same code builds where you are is not something it measured.
How many distinct signing keys filed a passing contract receipt. One is the author alone; more than one means somebody else built it too. A key is self-generated with nothing registered behind it, so it counts keys, not people.
MIT-0
Execution evidence
The declared environment and the signed runs are kept apart, so you can see exactly what this sample ran and where.
- Evidence basis
- Signed contract pass
- Verification receipts
- 2
- Signing keys that built it
- 2
Declared environment
rust linux x64 rust rust cargo
Verification-run environments
| Environment | Contract | Stages | Run |
|---|---|---|---|
| rust 1 · linux alpine/x64 · docker ed25519:d91480838ac982c9 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · cargo@1 |
2026-08-16 |
| rust 1 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · cargo@1 |
2026-08-18 |
Case
HOW- Goal
- Downcasting boxed trait objects in anyhow::Error fails silently because TypeId retains the outer Box, while .context() unwraps transparently and into_boxed_dyn_error bridges back to std::error::Error
- Packages
- Symbols
-
- anyhow::Error::from_boxed
- anyhow::Error::downcast_ref
- anyhow::Error::context
- anyhow::Error::into_boxed_dyn_error
- Environment
- rust
- Created
- 2026-08-16T07:54:48Z
Contract
- assert Error::from_boxed wraps Box<dyn StdError> such that downcast_ref to inner concrete type returns None
- assert recovering concrete error from boxed anyhow::Error requires explicit two-step downcast through Box<dyn StdError>
- assert anyhow context wrappers are transparently traversed by downcast_ref
- assert into_boxed_dyn_error converts anyhow::Error into Box<dyn std::error::Error> with preserved source chain
- assert Display prints top context while alternate Display joins chain and Debug formats Caused by section
Files
- Cargo.lock
- Cargo.toml
- NOTES.md
- csx.json
- src/lib.rs
Source
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "anyhow"
version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
[[package]]
name = "cargo-anyhow-version"
version = "0.1.0"
dependencies = [
"anyhow",
]
[package]
name = "cargo-anyhow-version"
version = "0.1.0"
edition = "2021"
license = "MIT-0"
[dependencies]
anyhow = "1.0.104"
# CSX Sample Notes: `anyhow` Boxed Trait Object Downcasting Traps, `.context()` Traversal, and `into_boxed_dyn_error`
## Search Known Solution Result
- `search_known_solution` returned COMPATIBLE hit:
- `sha256:3ea20ee94259c404473835653ef58f485134675ccf25369dc799be2861f4e232` (*"Interpolating inner errors into Display strings causes stutter during source chain traversal, while #[from] and #[source] differ in From generation and context preservation"* in `thiserror`).
- **Why this sample is distinct**: This sample targets `anyhow`'s runtime type-erasure trap when handling boxed dynamic trait objects (`Box<dyn std::error::Error + Send + Sync>`) vs direct concrete errors or `.context()` layers. When errors originating from dynamic dispatch or boxed APIs are converted into `anyhow::Error` via `Error::from_boxed`, `downcast_ref::<ConcreteType>()` silently returns `None` because the stored `TypeId` is `Box<dyn StdError + Send + Sync>`, whereas `.context()` wrappers are transparently traversed during downcasting. It also validates `into_boxed_dyn_error` (added in anyhow 1.0.98 to convert back to standard trait objects since `anyhow::Error` intentionally does not implement `std::error::Error`) and format string behaviors (`{}` vs `{:#}` vs `{:?}`).
## What a Model Would Have Written Instead
A model would assume `err.downcast_ref::<MyError>()` transparently unwraps boxed errors created via `Error::from_boxed(Box::new(MyError))` just like it unwraps `.context()` wrappers, or expect `Box<dyn std::error::Error>::from(err)` to compile directly on `anyhow::Error`.
## How the Wrong Version Fails
It fails silently with a green build: the code compiles without error, but pattern matches and downcast branches (`downcast_ref::<MyError>()`) evaluate to `None` at runtime, causing silent fallback to unhandled-error branches or dropping domain-specific error handling.
{"case":{"caseId":"case:sha256:4d7b0a80906f1cbf993433da1ad838b8759bac9ecf4d483a91879bceb9cc4b0d","contract":["assert Error::from_boxed wraps Box\u003cdyn StdError\u003e such that downcast_ref to inner concrete type returns None","assert recovering concrete error from boxed anyhow::Error requires explicit two-step downcast through Box\u003cdyn StdError\u003e","assert anyhow context wrappers are transparently traversed by downcast_ref","assert into_boxed_dyn_error converts anyhow::Error into Box\u003cdyn std::error::Error\u003e with preserved source chain","assert Display prints top context while alternate Display joins chain and Debug formats Caused by section"],"goal":"Downcasting boxed trait objects in anyhow::Error fails silently because TypeId retains the outer Box, while .context() unwraps transparently and into_boxed_dyn_error bridges back to std::error::Error","kind":"HOW","packages":["pkg:cargo/anyhow@1.0.104"],"schemaVersion":1,"symbols":["anyhow::Error::from_boxed","anyhow::Error::downcast_ref","anyhow::Error::context","anyhow::Error::into_boxed_dyn_error"]},"contractCommand":["cargo","test","--offline"],"environment":{"arch":"x64","ecosystem":"cargo","executionContext":"rust","language":"rust","os":"linux","packageManager":"cargo","runtime":"rust","schemaVersion":1},"license":"MIT-0","packages":["pkg:cargo/anyhow@1.0.104"],"schemaVersion":1,"symbols":["anyhow::Error::from_boxed","anyhow::Error::downcast_ref","anyhow::Error::context","anyhow::Error::into_boxed_dyn_error"],"verifierAdapter":"cargo@1"}
use std::error::Error as StdError;
use std::fmt;
use anyhow::{Context, Error};
#[derive(Debug, PartialEq, Eq)]
pub struct QueryError {
pub query_id: u64,
pub detail: String,
}
impl fmt::Display for QueryError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "query {} failed: {}", self.query_id, self.detail)
}
}
impl StdError for QueryError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_boxed_error_downcast_trap() {
let concrete = QueryError {
query_id: 42,
detail: "timeout reaching secondary replica".into(),
};
// Case A: Concrete error converted directly to anyhow::Error
let direct_err = Error::new(QueryError {
query_id: 42,
detail: "timeout reaching secondary replica".into(),
});
// Downcast directly to concrete type works:
assert!(direct_err.downcast_ref::<QueryError>().is_some());
// Case B: Error wrapped in Box<dyn StdError + Send + Sync> (e.g. from third-party crate or dynamic dispatch)
let boxed: Box<dyn StdError + Send + Sync + 'static> = Box::new(concrete);
let boxed_err = Error::from_boxed(boxed);
// TRAP: Naive assumption is that anyhow unpacks dyn trait objects so downcast_ref::<QueryError>() works.
// In reality, the stored TypeId is Box<dyn StdError + Send + Sync>, so downcast_ref to the inner type returns None!
assert!(
boxed_err.downcast_ref::<QueryError>().is_none(),
"downcast_ref to concrete type on boxed error silently returns None"
);
// To access the concrete type, one MUST downcast to Box<dyn StdError + Send + Sync> first:
let recovered_box = boxed_err
.downcast_ref::<Box<dyn StdError + Send + Sync>>()
.expect("must downcast to Box<dyn StdError + Send + Sync>");
let recovered_concrete = recovered_box
.downcast_ref::<QueryError>()
.expect("then downcast the inner trait object");
assert_eq!(recovered_concrete.query_id, 42);
}
#[test]
fn test_context_wrapper_unwraps_downcast_unlike_boxed_trait_objects() {
let concrete = QueryError {
query_id: 99,
detail: "connection refused".into(),
};
// Anyhow's .context() specifically traverses context layers during downcast_ref:
let err_with_context = Error::new(concrete)
.context("step 1: execute batch")
.context("step 2: transaction commit");
// Even with multiple context wrappers, downcast_ref finds the root QueryError:
let found = err_with_context.downcast_ref::<QueryError>();
assert!(found.is_some());
assert_eq!(found.unwrap().query_id, 99);
// Chain traversal lists contexts from outermost to innermost root cause:
let chain_items: Vec<String> = err_with_context.chain().map(|e| e.to_string()).collect();
assert_eq!(
chain_items,
vec![
"step 2: transaction commit",
"step 1: execute batch",
"query 99 failed: connection refused"
]
);
assert_eq!(
err_with_context.root_cause().to_string(),
"query 99 failed: connection refused"
);
}
#[test]
fn test_into_boxed_dyn_error_standard_conversion() {
let concrete = QueryError {
query_id: 101,
detail: "lock acquisition failure".into(),
};
let err = Error::new(concrete).context("operation failed");
// anyhow::Error does NOT implement std::error::Error (to prevent conflicting From implementations).
// In anyhow >= 1.0.98, into_boxed_dyn_error converts anyhow::Error into standard Box<dyn StdError + Send + Sync>:
let std_boxed: Box<dyn StdError + Send + Sync> = err.into_boxed_dyn_error();
assert_eq!(std_boxed.to_string(), "operation failed");
let inner_source = std_boxed.source().expect("source should link to inner cause");
assert_eq!(inner_source.to_string(), "query 101 failed: lock acquisition failure");
}
#[test]
fn test_formatting_display_vs_alternate_vs_debug() {
let err = Error::new(QueryError {
query_id: 200,
detail: "disk full".into(),
})
.context("save operation failed")
.context("api handler failed");
// 1. Display `{}` prints ONLY top-level context (not the full chain):
assert_eq!(format!("{}", err), "api handler failed");
// 2. Alternate Display `{:#}` prints the single-line causal chain joined by `: `:
assert_eq!(
format!("{:#}", err),
"api handler failed: save operation failed: query 200 failed: disk full"
);
// 3. Debug `{:?}` prints the multi-line "Caused by:" format:
let debug_repr = format!("{:?}", err);
assert!(debug_repr.starts_with("api handler failed\n\nCaused by:\n"));
assert!(debug_repr.contains("0: save operation failed\n"));
assert!(debug_repr.contains("1: query 200 failed: disk full"));
}
}