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
- Ensure each custom type has a unique `type_name`/identifier in its `#[custom_data]` registration.
- Verify the same binary/struct version reads the file that wrote it; re-generate code with the current macro expansion.
- Match on `Data::Custom` and log `custom.data.type_name()` before downcasting to identify the actual payload type.
- 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
- Keep one unique type_name per custom struct registration
- Downcast-check with `is::<T>()` before calling the generated TryFrom
- Round-trip test every custom type in CI to catch registration collisions
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
- Expected Custom data variant
- Actor type mismatch for '{id}': expected {expected_type:?},
- Expected {}, was different type
- Invalid config type for KrakenExecutionClientFactory. Expect
- ts_init column is not UInt64
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/160d86b27a9b4819.
Report an issue: GitHub.