#[derive(ToDataFrame)]
{
// Attributes available to this derive:
#[df_derive]
}
Expand description
Derive ToDataFrame for structs and tuple structs to generate fast conversions to Polars.
What this macro generates (paths configurable via #[df_derive(...)]):
- An implementation of
ToDataFramefor the annotated typeTproviding:fn to_dataframe(&self) -> PolarsResult<DataFrame>fn empty_dataframe() -> PolarsResult<DataFrame>fn schema() -> PolarsResult<Vec<(String, DataType)>>
- An implementation of
ColumnarforTprovidingfn columnar_to_dataframe(items: &[Self]) -> PolarsResult<DataFrame>andfn columnar_from_refs(items: &[&Self]) -> PolarsResult<DataFrame>. The direct slice method avoids the trait default’s temporary ref-vector allocation on top-level batch conversion; the borrowed method remains available for nested and generic composition.
Supported shapes and types:
- Named and tuple structs (tuple fields are named
field_{index}) - Nested structs are flattened using dot notation (e.g.,
outer.inner) - Wrappers
Option<T>andVec<T>in any nesting order, withVec<Struct>producing multiple list columns with avec_field.subfieldprefix - Primitive types:
String,bool, integer types includingi128/u128,std::num::NonZero*integer types,f32,f64 chrono::DateTime<Tz>andchrono::NaiveDateTime(default:Datetime(Milliseconds, None); override with#[df_derive(time_unit = "ms"|"us"|"ns")]).DateTime<Tz>stores the UTC instant; useas_stringwhen the textual timezone/offset matters.chrono::NaiveDate(Date, i32 days since 1970-01-01) andchrono::NaiveTime(Time, i64 ns since midnight); both have fixed encodings, no unit override.std::time::Duration,core::time::Duration, andchrono::Duration(alias forchrono::TimeDelta) →Duration(Nanoseconds)by default; override with#[df_derive(time_unit = "ms"|"us"|"ns")]. BareDurationis ambiguous and rejected.- Decimal backends written as bare
Decimalorrust_decimal::Decimal(default:Decimal(38, 10); override with#[df_derive(decimal(precision = N, scale = N))]). Custom backends opt in with explicitdecimal(...)and aDecimal128Encodeimpl.
Attributes:
- Container-level:
#[df_derive(trait = "path::ToDataFrame")]to set theToDataFrametrait path; theColumnarandDecimal128Encodepaths are inferred by replacing the last path segment withColumnar/Decimal128Encode. Optionally, set them explicitly with#[df_derive(columnar = "path::Columnar")]and#[df_derive(decimal128_encode = "path::Decimal128Encode")]. Acolumnaroverride must be paired withtraitto avoid mixed-runtime impls.decimal128_encodeis the dispatch point forrust_decimal::Decimal/bigdecimal::BigDecimal/ other decimal backends — see “Custom decimal backends” in the README for the trait contract. Explicit paths todf_derive::dataframe::ToDataFrameordf_derive_core::dataframe::ToDataFramekeep using the default runtime’s hidden dependency re-exports and cannot be paired with a customcolumnarpath; other explicit trait paths are treated as custom runtimes. - Field-level:
#[df_derive(skip)]to omit a field from generated schema andDataFrameoutput. Skipped fields are not type-analyzed, so this can be used for caches, handles, source metadata, or other helper values that should remain on the Rust struct but not become columns. Mutually exclusive with conversion attributes. - Field-level:
#[df_derive(as_string)]to stringify values viaDisplay(e.g., enums) during conversion, resulting inDataType::StringorList<String>. Generated encoders reuse aStringscratch buffer per field; the column builder still copies the formatted bytes. - Field-level:
#[df_derive(as_str)]to borrow&strviaAsRef<str>for the duration of the conversion. Same column type asas_stringbut avoidsDisplayformatting and the intermediate scratch buffer. The two attributes are mutually exclusive on a given field. - Field-level:
#[df_derive(as_binary)]to route aVec<u8>,&[u8], orCow<'_, [u8]>field through a PolarsBinarycolumn instead of the defaultList(UInt8)forVec<u8>. Accepted shapes:Vec<u8>,Option<Vec<u8>>,Vec<Vec<u8>>,Vec<Option<Vec<u8>>>,Option<Vec<Vec<u8>>>, and the same scalar/list shapes over&[u8]andCow<'_, [u8]>— bareu8,Option<u8>,Vec<Option<u8>>(BinaryViewcannot carry per-byte nulls), and non-u8leaves are rejected at parse time. Mutually exclusive withas_str,as_string,decimal(...), andtime_unit = "...". - Field-level:
#[df_derive(decimal(precision = N, scale = N))]to choose theDecimal(precision, scale)dtype for a built-in decimal path or to explicitly opt a custom/generic decimal backend intoDecimal128Encodedispatch. Polars requires1 <= precision <= 38;scalemay not exceedprecision. - Field-level:
#[df_derive(time_unit = "ms"|"us"|"ns")]to choose theDatetime(unit, None)/Duration(unit)dtype for a temporal field. Accepted bases arechrono::DateTime<Tz>,chrono::NaiveDateTime,std::time::Duration,core::time::Duration, andchrono::Duration. The chrono / std call used to derive the i64 matches the chosen unit, so values are not silently truncated.time_unit = "ns"onDateTime<Tz>orNaiveDateTimeis fallible on dates outside chrono’s supported nanosecond range (~1677–2262);time_unit = "ns"/"us"onchrono::Durationis fallible when the duration overflows i64 in the chosen unit; onstd::time::Durationevery unit is fallible (the value type isu128). All failures surface asPolarsError::ComputeErrorrather than silently corrupting data.time_unitis rejected onchrono::NaiveDateandchrono::NaiveTime(both have fixed encodings). - The
decimal(...)attribute can only be applied to decimal backend candidates: type paths namedDecimal, custom struct types, or generic type parameters that implementDecimal128Encode. It cannot be combined withas_str/as_string/time_uniton the same field. Thetime_unit = "..."attribute is also mutually exclusive withas_str/as_string.
Notes:
- Enums are not supported for derive.
- Generic structs are supported; the macro adds bounds only for the roles a
generic parameter actually plays (
ToDataFrame + Columnarfor nested dataframe payloads,AsRef<str>for genericas_str, andDecimal128Encodefor generic decimal backends). The unit type()is a valid generic payload (zero columns); directfield: ()fields are rejected. - All nested custom structs must also derive
ToDataFrame. - Obvious direct self-recursive nested fields using
Self, the bare deriving struct name,self::Type, orcrate::Typeare rejected after transparent wrapper peeling, includingBox<T>/Option<Box<T>>forms and tuple fields containing the same. - Empty structs:
to_dataframeyields a single-row, zero-columnDataFrame; the columnar path yields a zero-columnDataFramewithitems.len()rows.