Overview
The DeserializeRow trait enables deserialization of query result rows into Rust types. It provides type-safe conversion from CQL protocol format to custom structs, tuples, or other Rust types.
Most users should use the #[derive(DeserializeRow)] macro instead of implementing this trait manually.
Source: scylla-cql/src/deserialize/row.rs:111
Trait Definition
pub trait DeserializeRow<'frame, 'metadata>
where
Self: Sized,
{
fn type_check(specs: &[ColumnSpec]) -> Result<(), TypeCheckError>;
fn deserialize(
row: ColumnIterator<'frame, 'metadata>
) -> Result<Self, DeserializationError>;
}
Methods
type_check
Verifies that the row schema matches the Rust type’s expectations.
fn type_check(specs: &[ColumnSpec]) -> Result<(), TypeCheckError>
Column specifications from query result metadata
result
Result<(), TypeCheckError>
Ok(()) if types match, or type check error describing the mismatch
deserialize
Deserializes a row from column iterator.
fn deserialize(
row: ColumnIterator<'frame, 'metadata>
) -> Result<Self, DeserializationError>
row
ColumnIterator<'frame, 'metadata>
required
Iterator over columns in the result row
result
Result<Self, DeserializationError>
Deserialized Rust value, or deserialization error
ColumnIterator
Iterator over columns in a single row.
pub struct ColumnIterator<'frame, 'metadata> {
// ... internal fields
}
Methods
new
Creates a new column iterator.
pub fn new(specs: &'metadata [ColumnSpec<'metadata>], slice: FrameSlice<'frame>) -> Self
columns_remaining
Returns the number of columns remaining to iterate.
pub fn columns_remaining(&self) -> usize
type_check
Performs type check for a target row type.
pub fn type_check<RowT: DeserializeRow<'frame, 'metadata>>(
&self,
) -> Result<(), TypeCheckError>
Iterator Implementation
impl Iterator for ColumnIterator<'frame, 'metadata> {
type Item = Result<RawColumn<'frame, 'metadata>, DeserializationError>;
fn next(&mut self) -> Option<Self::Item>;
}
RawColumn
Represents a single unparsed column value.
pub struct RawColumn<'frame, 'metadata> {
pub index: usize,
pub spec: &'metadata ColumnSpec<'metadata>,
pub slice: Option<FrameSlice<'frame>>,
}
Column position in the row (0-based)
Column specification (name, type)
Serialized column value, or None for NULL
Built-in Implementations
ColumnIterator
impl DeserializeRow for ColumnIterator<'frame, 'metadata>
Pass-through implementation for raw column access.
Example:
let rows: Vec<ColumnIterator> = session
.query_unpaged("SELECT * FROM users", &[])
.await?
.rows_typed::<ColumnIterator>()?;
for mut row in rows {
while let Some(column) = row.next() {
let column = column?;
println!("Column {}: {:?}", column.spec.name(), column.slice);
}
}
Row (Legacy)
impl DeserializeRow for Row
Deserializes to scylla::value::Row, a vector of Option<CqlValue>.
Example:
use scylla::value::Row;
let rows: Vec<Row> = session
.query_unpaged("SELECT id, name FROM users", &[])
.await?
.rows_typed::<Row>()?;
for row in rows {
for column in row.columns {
println!("{:?}", column);
}
}
Tuples
impl<T0: DeserializeValue> DeserializeRow for (T0,)
impl<T0: DeserializeValue, T1: DeserializeValue> DeserializeRow for (T0, T1)
// ... up to 16 elements
Example:
// Query: SELECT id, name, email FROM users
let rows: Vec<(i32, String, Option<String>)> = session
.query_unpaged("SELECT id, name, email FROM users", &[])
.await?
.rows_typed::<(i32, String, Option<String>)>()?;
for (id, name, email) in rows {
println!("User {}: {} ({})", id, name, email.unwrap_or_default());
}
Derive Macro
The #[derive(DeserializeRow)] macro automatically implements DeserializeRow for structs.
Basic Usage
use scylla::macros::DeserializeRow;
#[derive(DeserializeRow)]
struct User {
id: i32,
name: String,
email: Option<String>,
}
let rows: Vec<User> = session
.query_unpaged("SELECT id, name, email FROM users", &[])
.await?
.rows_typed::<User>()?;
Field Attributes
#[scylla(rename = "...")]
Maps a Rust field to a different column name.
#[derive(DeserializeRow)]
struct User {
#[scylla(rename = "user_id")]
id: i32,
name: String,
}
// Matches CQL: SELECT user_id, name FROM users
#[scylla(skip)]
Skips deserializing a field (uses Default::default()).
#[derive(DeserializeRow)]
struct User {
id: i32,
name: String,
#[scylla(skip)]
local_cache: String, // Not present in query result
}
// Matches CQL: SELECT id, name FROM users
Type Attributes
#[scylla(crate = ...)]
Specifies the path to the scylla crate.
#[derive(DeserializeRow)]
#[scylla(crate = "scylla_cql")]
struct User {
id: i32,
name: String,
}
#[scylla(skip_name_checks)]
Disables column name validation, matching fields by position instead.
#[derive(DeserializeRow)]
#[scylla(skip_name_checks)]
struct UserTuple {
field_0: i32,
field_1: String,
}
Using skip_name_checks can lead to silent data corruption if column order changes. Use with extreme caution.
Error Types
BuiltinTypeCheckError
pub struct BuiltinTypeCheckError {
pub rust_name: &'static str,
pub cql_types: Vec<ColumnType<'static>>,
pub kind: BuiltinTypeCheckErrorKind,
}
BuiltinTypeCheckErrorKind
pub enum BuiltinTypeCheckErrorKind {
WrongColumnCount {
rust_cols: usize,
cql_cols: usize,
},
ColumnWithUnknownName {
column_index: usize,
column_name: String,
},
ValuesMissingForColumns {
column_names: Vec<&'static str>,
},
ColumnNameMismatch {
field_index: usize,
column_index: usize,
rust_column_name: &'static str,
db_column_name: String,
},
ColumnTypeCheckFailed {
column_index: usize,
column_name: String,
err: TypeCheckError,
},
DuplicatedColumn {
column_index: usize,
column_name: &'static str,
},
}
BuiltinDeserializationError
pub struct BuiltinDeserializationError {
pub rust_name: &'static str,
pub kind: BuiltinDeserializationErrorKind,
}
BuiltinDeserializationErrorKind
pub enum BuiltinDeserializationErrorKind {
ColumnDeserializationFailed {
column_index: usize,
column_name: String,
err: DeserializationError,
},
RawColumnDeserializationFailed {
column_index: usize,
column_name: String,
err: DeserializationError,
},
}
Examples
Struct Deserialization
use scylla::macros::DeserializeRow;
#[derive(DeserializeRow, Debug)]
struct User {
id: i32,
name: String,
email: Option<String>,
created_at: i64,
}
let rows: Vec<User> = session
.query_unpaged(
"SELECT id, name, email, created_at FROM users WHERE id > ?",
(100,)
)
.await?
.rows_typed::<User>()?;
for user in rows {
println!("{:?}", user);
}
Tuple Deserialization
// Simple query
let rows: Vec<(i32, String)> = session
.query_unpaged("SELECT id, name FROM users", &[])
.await?
.rows_typed::<(i32, String)>()?;
// With nullable columns
let rows: Vec<(i32, String, Option<String>)> = session
.query_unpaged("SELECT id, name, email FROM users", &[])
.await?
.rows_typed::<(i32, String, Option<String>)>()?;
Nested Struct Deserialization
use scylla::macros::{DeserializeRow, DeserializeValue};
#[derive(DeserializeValue, Debug)]
struct Address {
street: String,
city: String,
zip: i32,
}
#[derive(DeserializeRow, Debug)]
struct User {
id: i32,
name: String,
address: Address, // UDT column
}
let rows: Vec<User> = session
.query_unpaged("SELECT id, name, address FROM users", &[])
.await?
.rows_typed::<User>()?;
Field Renaming
#[derive(DeserializeRow, Debug)]
struct User {
#[scylla(rename = "user_id")]
id: i32,
#[scylla(rename = "full_name")]
name: String,
}
// Matches: SELECT user_id, full_name FROM users
let rows: Vec<User> = session
.query_unpaged("SELECT user_id, full_name FROM users", &[])
.await?
.rows_typed::<User>()?;
Skipping Fields
#[derive(DeserializeRow, Debug)]
struct User {
id: i32,
name: String,
#[scylla(skip)]
cached_data: Option<String>, // Computed locally, not from DB
}
// Only SELECT id, name FROM users needed
Raw Column Access
let mut result = session
.query_unpaged("SELECT * FROM users", &[])
.await?;
let rows: Vec<ColumnIterator> = result.rows_typed::<ColumnIterator>()?;
for mut row in rows {
while let Some(column) = row.next() {
let column = column?;
match column.slice {
Some(data) => println!("{}: {} bytes", column.spec.name(), data.len()),
None => println!("{}: NULL", column.spec.name()),
}
}
}
Best Practices
- Use tuples for simple queries with few columns
- Use structs with derive for complex result types
- Handle NULL with Option<T> for nullable columns
- Use meaningful field names that match column names
- Add type checking early in development to catch schema mismatches
Type Safety: The driver performs runtime type checking. Column name and type mismatches will result in errors, not silent data corruption.
See Also