Sample
thiserror 2.0.20: #[error(transparent)] forwards both Display and Error::source to the inner error rather than wrapping the inner error as the cause layer
Verified sample for cargo thiserror 2.0.20: #[error(transparent)] forwards both Display and Error::source to the inner error rather than wrapping the inner…
sha256:115c8c422a1ed72be811ab6ecb1f995940e32fd286cfe04c38e865c43df7feb6
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
- 3
- Signing keys that built it
- 3
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-17 |
| rust 1 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · cargo@1 |
2026-08-18 |
| rust 1 · linux alpine/x64 · docker ed25519:c1973797be207ac4 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · cargo@1rust:1-alpine@sha256:a10e64dd139b… |
2026-09-08 |
Case
HOW- Goal
- #[error(transparent)] forwards both Display and Error::source to the inner error rather than wrapping the inner error as the cause layer
- Packages
- Symbols
-
- thiserror::Error
- Environment
- rust
- Created
- 2026-08-17T18:49:30Z
Contract
- Calling Error::source on an error deriving Error with #[error(transparent)] delegates directly to inner.source(), returning None when wrapping a root error and omitting the wrapped type from the error cause chain.
- Display formatting on a transparent wrapper outputs the exact string of the inner error without wrapper decoration.
- Standard #[error("...")] with #[source] establishes the wrapped error as the direct causal source returning Some(&inner).
- Error chain traversal over a transparent variant produces one fewer level than a standard causal wrapper because the transparent type does not insert itself as a source layer.
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 = "proc-macro2"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
dependencies = [
"proc-macro2",
]
[[package]]
name = "syn"
version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "2.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-api-sample"
version = "0.1.0"
dependencies = [
"thiserror",
]
[[package]]
name = "thiserror-impl"
version = "2.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[package]
name = "thiserror-api-sample"
version = "0.1.0"
edition = "2021"
[dependencies]
thiserror = "2.0.20"
# thiserror: Transparent Delegation vs Causal Wrapping
## Known Solutions Search
A search on `thiserror` returned known samples (e.g., `sha256:07621dbfaa6b7cf6bab4b7d50320b3a8045196e7de5fc638a46a9cc79b0aa38b` / `sha256:3ea20ee94259c404473835653ef58f485134675ccf25369dc799be2861f4e232`) addressing Display interpolation stutter during source chain traversal and differences between `#[from]` and `#[source]`.
This sample investigates a distinct behavior in `thiserror`: the delegation semantics of `#[error(transparent)]` compared to standard causal wrappers.
## Pinned Version
- `thiserror`: `2.0.20`
- `thiserror-impl`: `2.0.20`
## Observed Behavior
When deriving `thiserror::Error` with `#[error(transparent)]`:
1. **`Display` Delegation**: Formatting (`Display::fmt`) delegates directly to `Display::fmt` of the inner error without adding any wrapper text or formatting.
2. **`Error::source` Delegation**: Calling `source()` delegates directly to `self.0.source()`. It does **not** return `Some(&self.0)`.
- If the inner error is a root cause whose `source()` returns `None`, the transparent wrapper's `source()` also returns `None`.
- If the inner error is an intermediate layer whose `source()` returns `Some(root)`, the transparent wrapper's `source()` returns `Some(root)`, effectively omitting the wrapped type from appearing as a separate link in the causal chain.
3. **Comparison with Standard Wrapping**: In contrast, standard `#[error("...")]` annotations with `#[source]` or `#[from]` create a new causal tier where `outer.source()` returns `Some(&inner)`.
## Failure Mode
When callers assume `#[error(transparent)]` automatically sets the inner error as the wrapper's `source()`, code that attempts `err.source().unwrap()` on a transparently wrapped root error fails loudly with a panic (`called Option::unwrap() on a None value`). In error reporter utilities iterating through cause chains via `.source()`, the wrapped type will be missing from the traversed steps.
{"case":{"believed":"#[error(transparent)] causes Error::source to return the wrapped error as Some(\u0026inner)","caseId":"case:sha256:f1078dba9022f2b4a25437c9f3f0f6d687680b8dda4c9c534a2b8cabb563a922","contract":["Calling Error::source on an error deriving Error with #[error(transparent)] delegates directly to inner.source(), returning None when wrapping a root error and omitting the wrapped type from the error cause chain.","Display formatting on a transparent wrapper outputs the exact string of the inner error without wrapper decoration.","Standard #[error(\"...\")] with #[source] establishes the wrapped error as the direct causal source returning Some(\u0026inner).","Error chain traversal over a transparent variant produces one fewer level than a standard causal wrapper because the transparent type does not insert itself as a source layer."],"goal":"#[error(transparent)] forwards both Display and Error::source to the inner error rather than wrapping the inner error as the cause layer","kind":"HOW","packages":["pkg:cargo/thiserror@2.0.20"],"schemaVersion":1,"symbols":["thiserror::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/thiserror@2.0.20"],"schemaVersion":1,"symbols":["thiserror::Error"],"verifierAdapter":"cargo@1"}
use std::error::Error;
use std::fmt;
use thiserror::Error;
/// A leaf root error with no underlying source.
#[derive(Debug)]
pub struct RootError {
pub reason: &'static str,
}
impl fmt::Display for RootError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "root cause: {}", self.reason)
}
}
impl Error for RootError {}
/// An intermediate error that wraps a RootError as its source.
#[derive(Debug)]
pub struct IntermediateError {
pub message: &'static str,
pub source: RootError,
}
impl fmt::Display for IntermediateError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "intermediate: {}", self.message)
}
}
impl Error for IntermediateError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
Some(&self.source)
}
}
/// Transparent tuple struct wrapping a root error.
#[derive(Error, Debug)]
#[error(transparent)]
pub struct TransparentRoot(pub RootError);
/// Transparent named struct wrapping an intermediate error.
#[derive(Error, Debug)]
#[error(transparent)]
pub struct TransparentIntermediate {
pub source: IntermediateError,
}
/// Enum containing both transparent and standard causal variants.
#[derive(Error, Debug)]
pub enum ContainerError {
#[error(transparent)]
Transparent(#[from] IntermediateError),
#[error("standard wrapper context: {context}")]
Standard {
context: &'static str,
#[source]
source: IntermediateError,
},
}
/// Collects the Display representations across the entire source() chain.
pub fn collect_chain(err: &dyn Error) -> Vec<String> {
let mut chain = Vec::new();
let mut current = Some(err);
while let Some(e) = current {
chain.push(e.to_string());
current = e.source();
}
chain
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_transparent_forwards_display_and_source() {
let root = RootError {
reason: "permission denied",
};
let trans_root = TransparentRoot(root);
// 1. Display forwards directly to inner root error
assert_eq!(trans_root.to_string(), "root cause: permission denied");
// 2. Error::source() delegates to inner.source() (which is None),
// NOT returning Some(&self.0).
assert!(
trans_root.source().is_none(),
"transparent root error must return None from source(), not Some(&inner)"
);
let intermediate = IntermediateError {
message: "database query failed",
source: RootError {
reason: "connection reset",
},
};
let trans_inter = TransparentIntermediate {
source: intermediate,
};
// 3. Display forwards directly to inner intermediate error
assert_eq!(
trans_inter.to_string(),
"intermediate: database query failed"
);
// 4. Error::source() delegates directly to intermediate.source() (RootError),
// skipping intermediate from being its own source layer.
let delegated_source = trans_inter
.source()
.expect("should delegate to intermediate.source()");
assert_eq!(delegated_source.to_string(), "root cause: connection reset");
assert!(delegated_source.source().is_none());
}
#[test]
fn test_transparent_vs_standard_chain_traversal() {
let root = RootError {
reason: "disk space exhausted",
};
let intermediate = IntermediateError {
message: "failed to write log batch",
source: root,
};
// Transparent variant:
let trans_enum = ContainerError::Transparent(intermediate);
let trans_chain = collect_chain(&trans_enum);
// Chain contains only 2 entries because Transparent delegates source() directly to RootError
assert_eq!(
trans_chain,
vec![
"intermediate: failed to write log batch",
"root cause: disk space exhausted",
]
);
// Standard causal variant:
let root2 = RootError {
reason: "disk space exhausted",
};
let intermediate2 = IntermediateError {
message: "failed to write log batch",
source: root2,
};
let std_enum = ContainerError::Standard {
context: "flush failed",
source: intermediate2,
};
let std_chain = collect_chain(&std_enum);
// Chain contains 3 entries because Standard creates a distinct error layer
assert_eq!(
std_chain,
vec![
"standard wrapper context: flush failed",
"intermediate: failed to write log batch",
"root cause: disk space exhausted",
]
);
}
}