CodeSampleX

Пример

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

Проверенный пример — cargo thiserror 2.0.20: #[error(transparent)] forwards both Display and Error::source to the inner error rather than wrapping the inner…

sha256:115c8c422a1ed72be811ab6ecb1f995940e32fd286cfe04c38e865c43df7feb6

Эта сеть предлагает одно: образец, который собирается. Она запустила его в песочнице и сохранила подписанную квитанцию. Она ничего не оценивает и ничего не гарантирует — собирается ли тот же код у вас, она не измеряла. Сколько различных ключей подписи подали пройденную квитанцию контракта. Один — только автор; больше одного — значит, кто-то ещё тоже собрал. Ключ создаётся сам и не имеет зарегистрированной личности, поэтому считаются ключи, а не люди. MIT-0

Свидетельства выполнения

Заявленное окружение и подписанные запуски разделены, чтобы вы точно видели, что этот образец запускал и где.

Основа свидетельства
Подписанный контракт пройден
Квитанции проверки
3
Ключи подписи, собравшие его
3
Заявленная среда rust linux x64 rust rust cargo

Среды запусков проверки

Окружение Контракт Этапы Запуск
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

Кейс

HOW
Цель
#[error(transparent)] forwards both Display and Error::source to the inner error rather than wrapping the inner error as the cause layer
Пакеты
Символы
  • thiserror::Error
Окружение
rust
Создан
2026-08-17T18:49:30Z

Контракт

  1. 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.
  2. Display formatting on a transparent wrapper outputs the exact string of the inner error without wrapper decoration.
  3. Standard #[error("...")] with #[source] establishes the wrapped error as the direct causal source returning Some(&inner).
  4. 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.

Файлы

  • Cargo.lock
  • Cargo.toml
  • NOTES.md
  • csx.json
  • src/lib.rs

Скачать артефакт с исходным кодом (tar.gz)

Исходный код

Cargo.lock
# 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"
Cargo.toml
[package]
name = "thiserror-api-sample"
version = "0.1.0"
edition = "2021"

[dependencies]
thiserror = "2.0.20"
NOTES.md
# 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.
csx.json
{"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"}
src/lib.rs
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",
            ]
        );
    }
}

Исходный сидер

csx-seed