nautechsystems/nautilus_trader · error · anyhow::Error

Expected {}

Error message

Expected {}

What it means

Generated by the `#[custom_data]` macro's `TryFrom<Data>` impl: a `Data::Custom` wrapper was downcast to the concrete type but `as_any().downcast_ref::<Self>()` returned None, meaning the boxed payload is a different concrete type than the impl targets. The macro bails to signal a failed downcast during enum-to-concrete conversion. It indicates type confusion between what was wrapped and what is being unwrapped.

Source

Thrown at crates/persistence/macros/src/custom.rs:1161

        }
    };
    let from_impl = quote! {
        impl #generics std::convert::From<#name #generics> for nautilus_model::data::Data {
            fn from(value: #name #generics) -> Self {
                nautilus_model::data::Data::Custom(nautilus_model::data::CustomData::from_arc(std::sync::Arc::new(value)))
            }
        }
    };
    let try_from_impl = quote! {
        impl #generics std::convert::TryFrom<nautilus_model::data::Data> for #name #generics {
            type Error = anyhow::Error;
            fn try_from(value: nautilus_model::data::Data) -> std::result::Result<Self, Self::Error> {
                match value {
                    nautilus_model::data::Data::Custom(custom) => {
                        if let Some(c) = custom.data.as_any().downcast_ref::<Self>() {
                            Ok(std::clone::Clone::clone(c))
                        } else {
                            anyhow::bail!("Expected {}", #name_str)
                        }
                    }
                    _ => anyhow::bail!("Expected Custom data variant"),
                }
            }
        }
    };
    (catalog_path_prefix_impl, from_impl, try_from_impl)
}

#[expect(
    clippy::too_many_lines,
    reason = "PyO3 token generation is clearer with related methods kept together"
)]
fn gen_pymethods_impl(ctx: &ExpansionContext<'_>) -> TokenStream {
    let name = ctx.name;
    let generics = ctx.generics;
    let field_list = ctx.field_list;

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Ensure each custom type has a unique `type_name`/identifier in its `#[custom_data]` registration.
  2. Verify the same binary/struct version reads the file that wrote it; re-generate code with the current macro expansion.
  3. Match on `Data::Custom` and log `custom.data.type_name()` before downcasting to identify the actual payload type.
  4. If types legitimately differ, route to the correct `TryFrom` impl instead of forcing this one.

Example fix

// before
let typed = MyType::try_from(data)?;
// after
let typed = match &data {
    nautilus_model::data::Data::Custom(c) if c.data.type_name() == MyType::type_name() =>
        MyType::try_from(data.clone())?,
    other => anyhow::bail!("unexpected data: {}", other),
};
Defensive patterns

Strategy: type-guard

Validate before calling

fn is_my_type(data: &nautilus_model::data::Data) -> bool {
    matches!(data, nautilus_model::data::Data::Custom(c)
        if c.data.as_any().is::<MyType>())
}

Type guard

fn as_my_type(data: &nautilus_model::data::Data) -> Option<&MyType> {
    match data {
        nautilus_model::data::Data::Custom(c) => c.data.as_any().downcast_ref::<MyType>(),
        _ => None,
    }
}

Try / catch

match MyType::try_from(data) {
    Ok(t) => handle(t),
    Err(e) if e.to_string().starts_with("Expected ") => skip_or_log(data),
    Err(e) => return Err(e.into()),
}

Prevention

When it happens

Trigger: Calling `try_from(Data::Custom(...))` (or `TryInto`/record decoding paths that use it) where the inner payload's `type_id` does not match the target struct, e.g. two distinct custom types share the same registered type name, or a serialized type was re-registered under a different Rust type in the process.

Common situations: Macro-generated catalog round-trips where a custom data type was registered twice under the same `type_name` with different structs; decoding a Parquet file written by a different version of the struct; mixing custom data types from different crates in one catalog session.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/160d86b27a9b4819. Report an issue: GitHub.