Overview
The new deserialization API addresses two main limitations of the old approach:- Performance: The old API parsed all data into
CqlValueenums first, then converted them to target types. The new API deserializes directly from raw bytes. - Type Safety: The new API has full type information during deserialization, enabling better compile-time and runtime type checking.
Old Traits
The legacy API worked by deserializing query responses to a sequence ofRows, where Row is just a Vec<Option<CqlValue>>, and CqlValue is an enum representing any CQL value.
FromRow
Row into a custom type.
FromCqlVal
Derive Macros
#[derive(FromRow)]: GeneratedFromRowimplementations for structs#[derive(FromUserType)]: GeneratedFromCqlValimplementations for UDT deserialization
New Traits
The new API introduces two analogous traits that work with raw serialized data instead of pre-parsed values.DeserializeRow<'frame, 'metadata>
type_check: Validates types before deserialization- Receives raw data via
ColumnIteratorinstead of parsedRow - Lifetime parameters enable zero-copy deserialization
DeserializeValue<'frame, 'metadata>
- Separate
type_checkmethod for validation - Works with raw
FrameSliceinstead ofCqlValue - Supports zero-copy deserialization for borrowed types
Derive Macros
#[derive(DeserializeRow)]: GeneratesDeserializeRowimplementations#[derive(DeserializeValue)]: GeneratesDeserializeValueimplementations for UDTs
The new macros have different default behavior: they match struct fields to column/UDT field names by default, rather than relying solely on position.
Migration Examples
Basic Query Results
Before (0.14 and earlier):- Call
.into_rows_result()?on the query result - Use
.rows()instead of.rows_typed() - The iterator borrows from the result instead of consuming it
Zero-Copy Deserialization
The new API supports borrowing directly from the result frame to avoid allocations: Before:Paged Query Iterator
Before:- Use
.rows_stream()instead of.into_typed() - Type can be inferred from usage or specified with turbofish
Currently,
QueryPager/TypedRowStream do not support deserialization of borrowed types due to Rust limitations with lending streams. For zero-copy deserialization, use the single-page API (query_single_page).User-Defined Types (UDTs)
Before:Macro Attribute Changes
FromRow vs. DeserializeRow
The old FromRow macro expected columns in the same order as struct fields. To replicate this behavior with DeserializeRow:
FromUserType vs. DeserializeValue
The old FromUserType expected UDT fields in the same order as struct fields. To replicate this behavior:
Available Attributes
ForDeserializeRow:
flavor = "enforce_order": Columns must appear in the same order as struct fieldsskip_name_checks: Don’t validate field names against column names
DeserializeValue:
flavor = "enforce_order": UDT fields must appear in the same order as struct fields
QueryResult API Changes
Removed Direct Field Access
TheQueryResult::rows field is no longer publicly accessible.
Before:
Helper Methods
All helper methods now require type parameters:| Old Method | New Method |
|---|---|
.rows() | .rows::<T>() where T: DeserializeRow |
.rows_typed::<T>() | .rows::<T>() |
.first_row() | .first_row::<T>() |
.first_row_typed::<T>() | .first_row::<T>() |
Custom Trait Implementations
If you have hand-written implementations of the old traits, you’ll need to write new implementations for the new traits. Example - CustomFromRow implementation:
Performance Benefits
The new deserialization API provides significant performance improvements:- Zero-copy deserialization: Borrowed types (
&str,&[u8]) can reference data directly from the response frame - No intermediate allocations: Data is deserialized directly to the target type without creating
CqlValueinstances - Better type checking: Type validation happens before deserialization, avoiding wasted work on type mismatches
Summary
The new deserialization API in version 0.15 provides:- Better performance through zero-copy deserialization
- Improved type safety with explicit type checking
- More ergonomic API with name-based field matching
- Clearer separation between type validation and deserialization
- Replace
FromRowwithDeserializeRow - Replace
FromCqlValwithDeserializeValue - Update query result handling to use
.into_rows_result()and.rows::<T>() - Update iterator queries to use
.rows_stream() - Consider using borrowed types (
&str) for better performance - Add
#[scylla(flavor = "enforce_order")]if you need position-based matching
