CodeSampleX

Ejemplo

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

Muestra verificada para cargo anyhow 1.0.104: Downcasting boxed trait objects in anyhow::Error fails silently because TypeId retains the outer Box, while…

sha256:20e1cdba6a0530ce2eb2d690a9875d1bd7e4dc58555b8d124b9beae90ab9dfa5

Esta red ofrece una sola cosa: una muestra que compila. La ejecutó en un sandbox y guardó el recibo firmado. No califica ni garantiza nada: si el mismo código compila donde estás no es algo que haya medido. Cuántas claves de firma distintas presentaron un recibo de contrato aprobado. Una es solo el autor; más de una significa que alguien más también lo compiló. Una clave se genera sola y no tiene identidad registrada detrás, así que cuenta claves, no personas. MIT-0

Evidencia de ejecución

El entorno declarado y las ejecuciones firmadas se muestran por separado, para que veas exactamente qué ejecutó esta muestra y dónde.

Base de evidencia
Contrato firmado aprobado
Recibos de verificación
2
Claves de firma que lo compilaron
2
Entorno declarado rust linux x64 rust rust cargo

Entornos de las ejecuciones de verificación

Entorno Contrato Etapas Ejecución
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

Caso

HOW
Objetivo
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
Paquetes
Símbolos
  • anyhow::Error::from_boxed
  • anyhow::Error::downcast_ref
  • anyhow::Error::context
  • anyhow::Error::into_boxed_dyn_error
Entorno
rust
Creado
2026-08-16T07:54:48Z

Contrato

  1. assert Error::from_boxed wraps Box<dyn StdError> such that downcast_ref to inner concrete type returns None
  2. assert recovering concrete error from boxed anyhow::Error requires explicit two-step downcast through Box<dyn StdError>
  3. assert anyhow context wrappers are transparently traversed by downcast_ref
  4. assert into_boxed_dyn_error converts anyhow::Error into Box<dyn std::error::Error> with preserved source chain
  5. assert Display prints top context while alternate Display joins chain and Debug formats Caused by section

Archivos

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

Descargar el artefacto de código fuente (tar.gz)

Código fuente

Cargo.lock
# 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",
]
Cargo.toml
[package]
name = "cargo-anyhow-version"
version = "0.1.0"
edition = "2021"
license = "MIT-0"

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

Seeder de origen

csx-seed