Loading page…
Rust walkthroughs
Loading page…
serde::de::DeserializeOwned differ from serde::de::Deserialize<'de> for lifetime flexibility?serde::de::DeserializeOwned is a trait alias for for<'de> Deserialize<'de>, meaning it can deserialize into a type that owns its data without borrowing from the input. serde::de::Deserialize<'de> is the fundamental trait that can borrow data from the input source (like borrowing string slices from JSON text), but this requires the lifetime 'de to be propagated through the entire deserialization chain. The key difference is ownership: DeserializeOwned produces values that own all their data and have no lifetime constraints, while Deserialize<'de> allows zero-copy deserialization where strings can reference the original input buffer. Use DeserializeOwned when you need to return deserialized values from functions or store them in structs without lifetime parameters. Use Deserialize<'de> when you want to avoid allocations by borrowing from the input.
use serde::Deserialize;
// Deserialize<'de> allows borrowing from input
#[derive(Deserialize)]
struct Config<'a> {
name: &'a str, // Borrows from input
version: String, // Owns its data
}
fn main() {
let json = r#"{"name": "myapp", "version": "1.0"}"#;
let config: Config = serde_json::from_str(json).unwrap();
// config.name borrows from json string
// config can only live as long as json
println!("Name: {}, Version: {}", config.name, config.version);
// This wouldn't compile:
// let config: Config<'_> = serde_json::from_str(json).unwrap();
// drop(json); // Error: json borrowed by config
// println!("{}", config.name);
}Deserialize<'de> enables zero-copy deserialization but constrains lifetimes.
use serde::de::DeserializeOwned;
use serde::Deserialize;
// DeserializeOwned = for<'de> Deserialize<'de>
// No lifetime parameters needed
#[derive(Deserialize)]
struct OwnedConfig {
name: String, // Owns its data
version: String, // Owns its data
}
fn deserialize_owned<T: DeserializeOwned>(json: &str) -> T {
// Can return T without lifetime constraints
serde_json::from_str(json).unwrap()
}
fn main() {
let json = r#"{"name": "myapp", "version": "1.0"}"#;
let config: OwnedConfig = deserialize_owned(json);
// config owns all its data
// json can be dropped
drop(json);
// config still valid
println!("Name: {}, Version: {}", config.name, config.version);
}DeserializeOwned produces values that own all their data.
use serde::Deserialize;
use serde_json::Value;
// Function that uses Deserialize<'de> directly
fn deserialize_with_lifetime<'de, T>(json: &'de str) -> T
where
T: Deserialize<'de>,
{
serde_json::from_str(json).unwrap()
}
fn main() {
let json = r#""hello""#.to_string();
// String can borrow from input
let s: String = deserialize_with_lifetime(&json);
println!("Owned string: {}", s);
// &str can also borrow from input
let s: &str = deserialize_with_lifetime(&json);
println!("Borrowed string: {}", s);
// But s borrows from json, limiting its lifetime
// Problem: can't return value without lifetime
fn try_return<'de, T: Deserialize<'de>>(json: &'de str) -> T {
// This works for types that borrow from 'de
// But caller must handle lifetime
serde_json::from_str(json).unwrap()
}
// If T borrows from json, caller must keep json alive
}Deserialize<'de> propagates the lifetime constraint to callers.
use serde::de::DeserializeOwned;
use serde::Deserialize;
#[derive(Deserialize, Debug)]
struct User {
name: String,
email: String,
}
// With DeserializeOwned: no lifetime in return type
fn parse_user_owned(json: &str) -> Result<User, serde_json::Error> {
serde_json::from_str(json) // T: DeserializeOwned bound satisfied
}
// Equivalent function with explicit lifetime
fn parse_user_de<'de>(json: &'de str) -> Result<User, serde_json::Error> {
serde_json::from_str(json)
// But User doesn't borrow from 'de, so DeserializeOwned works
}
fn main() {
let user = parse_user_owned(r#"{"name": "Alice", "email": "alice@example.com"}"#);
println!("User: {:?}", user);
// parse_user_owned can return User without lifetime
// Because User owns all its data (String fields)
}DeserializeOwned simplifies function signatures when types own their data.
use serde::Deserialize;
#[derive(Deserialize)]
struct BorrowedData<'a> {
// This struct can borrow from input
reference: &'a str, // Borrows from input buffer
cow: std::borrow::Cow<'a, str>, // Can borrow or own
}
#[derive(Deserialize)]
struct OwnedData {
// This struct owns everything
reference: String, // Owns its data
cow: String, // Owns its data
}
fn main() {
let json = r#"{"reference": "hello", "cow": "world"}"#;
// BorrowedData can borrow
let borrowed: BorrowedData = serde_json::from_str(json).unwrap();
println!("Borrowed: {}", borrowed.reference);
// borrowed.reference points directly into json
// OwnedData owns its data
let owned: OwnedData = serde_json::from_str(json).unwrap();
println!("Owned: {}", owned.reference);
// Difference: BorrowedData can't outlive json
// OwnedData is independent of json lifetime
}Zero-copy deserialization uses Deserialize<'de> to avoid allocations.
use serde::de::DeserializeOwned;
use serde::Deserialize;
use std::io::Read;
// WRONG: Can't work with DeserializeOwned for borrowing types
// fn from_reader_owned<R: Read, T: DeserializeOwned>(reader: R) -> T {
// // Problem: reader provides bytes that might be borrowed
// // But DeserializeOwned means T can't borrow
// // This actually works but loses zero-copy opportunity
// }
// With Deserialize<'de>: can support borrowing
fn from_reader_borrowing<R: Read, T>(reader: R) -> Result<T, serde_json::Error>
where
T: for<'de> Deserialize<'de>, // Actually same as DeserializeOwned
{
// But we can't get zero-copy from a Read
// Because the bytes are consumed, not retained
serde_json::from_reader(reader)
}
// The key insight: from_reader can't support borrowing anyway
// Because Read consumes the source
// So DeserializeOwned is appropriate there
fn main() {
let json = r#"{"name": "Alice"}"#;
let mut cursor = std::io::Cursor::new(json);
// from_reader consumes the data, so T must own
let result: serde_json::Value =
serde_json::from_reader(&mut cursor).unwrap();
}
// from_str CAN support borrowing because &str persists
fn from_str_borrowing<'de, T: Deserialize<'de>>(json: &'de str) -> T {
serde_json::from_str(json).unwrap()
}Whether borrowing is possible depends on the data source.
use serde::de::DeserializeOwned;
use serde::Deserialize;
use std::collections::HashMap;
// Generic function that stores deserialized values
fn load_config<T: DeserializeOwned>(path: &str) -> T {
let content = std::fs::read_to_string(path).unwrap();
serde_json::from_str(&content).unwrap()
// content is dropped, T must own its data
}
// Generic struct that holds deserialized values
struct ConfigCache<T: DeserializeOwned> {
cache: HashMap<String, T>,
}
impl<T: DeserializeOwned> ConfigCache<T> {
fn new() -> Self {
ConfigCache { cache: HashMap::new() }
}
fn load(&mut self, name: &str, json: &str) {
let config: T = serde_json::from_str(json).unwrap();
self.cache.insert(name.to_string(), config);
// json can be discarded - T owns its data
}
}
fn main() {
#[derive(Deserialize)]
struct ServerConfig {
host: String,
port: u16,
}
let mut cache = ConfigCache::<ServerConfig>::new();
cache.load("prod", r#"{"host": "localhost", "port": 8080}"#);
}DeserializeOwned is essential for storing deserialized values.
use serde::Deserialize;
#[derive(Deserialize, Debug)]
struct LogEntry<'a> {
#[serde(borrow)]
timestamp: &'a str,
#[serde(borrow)]
level: &'a str,
#[serde(borrow)]
message: &'a str,
}
fn process_logs_zero_copy(json: &str) -> Vec<LogEntry<'_>> {
// Zero-copy: each LogEntry borrows from json
// No String allocations for text fields
serde_json::from_str(json).unwrap()
}
fn process_logs_owned(json: &str) -> Vec<OwnedLogEntry> {
// Copies all strings into owned types
// More allocations but values are independent
serde_json::from_str(json).unwrap()
}
#[derive(Deserialize, Debug)]
struct OwnedLogEntry {
timestamp: String,
level: String,
message: String,
}
fn main() {
let json = r#"[{"timestamp": "2024-01-01", "level": "INFO", "message": "Started"}]"#;
// Zero-copy
let entries = process_logs_zero_copy(json);
println!("Entry: {:?}", entries[0]);
// entries[0].timestamp points into json
// Owned
let owned = process_logs_owned(json);
println!("Owned: {:?}", owned[0]);
// owned[0].timestamp is a separate String
}Zero-copy deserialization avoids allocations but constrains lifetimes.
use serde::Deserialize;
use serde::de::DeserializeOwned;
// DeserializeOwned is defined as:
// pub trait DeserializeOwned: for<'de> Deserialize<'de> {}
// This means: implements Deserialize for ALL lifetimes 'de
// Therefore: the result cannot borrow from any specific 'de
fn demonstrate_for_de<T>()
where
T: for<'de> Deserialize<'de>,
// Same as T: DeserializeOwned
{
// T can be deserialized from any lifetime
// T cannot borrow from the input
}
// Contrast with specific lifetime
fn specific_lifetime<'de, T>(json: &'de str) -> T
where
T: Deserialize<'de>,
{
// T can borrow from json with lifetime 'de
serde_json::from_str(json).unwrap()
}
fn main() {
// String: for<'de> Deserialize<'de> (owns data)
let s: String = specific_lifetime(r#""hello""#);
// &str: Deserialize<'de> for specific 'de (borrows)
// But not for<'de> Deserialize<'de> (can't borrow from all lifetimes)
fn works_with_owned<T: DeserializeOwned>(json: &str) -> T {
serde_json::from_str(json).unwrap()
}
let s: String = works_with_owned(r#""hello""#); // Works
// let s: &str = works_with_owned(r#""hello""#); // Doesn't compile!
}for<'de> means "for all lifetimes", preventing borrowing from any specific one.
use serde::{Deserialize, de::DeserializeOwned};
use std::borrow::Cow;
#[derive(Deserialize)]
struct Flexible<'a> {
// Can borrow or own, depending on content
#[serde(borrow)]
name: Cow<'a, str>,
}
#[derive(Deserialize)]
struct StrictlyOwned {
// Always owns
name: String,
}
// When to use Deserialize<'de>:
// - You want zero-copy deserialization
// - You're deserializing from &str or &[u8]
// - Your types can borrow from input
// - Performance matters more than convenience
// When to use DeserializeOwned:
// - You need to return values from functions
// - You're storing in collections/structs
// - You're deserializing from Read (consumed source)
// - Your types own all their data anyway
// - Simplicity is more important than zero-copy
fn main() {
let json = r#"{"name": "Alice"}"#;
// Deserialize<'de> allows borrowing
let flexible: Flexible = serde_json::from_str(json).unwrap();
// flexible.name borrows "Alice" from json
// DeserializeOwned requires owning
let owned: StrictlyOwned = serde_json::from_str(json).unwrap();
// owned.name owns "Alice" separately
}Choose based on whether you need zero-copy or lifetime independence.
use serde::{Deserialize, de::DeserializeOwned};
use std::collections::HashMap;
// PATTERN 1: Generic cache/store - use DeserializeOwned
struct Cache<T: DeserializeOwned> {
data: HashMap<String, T>,
}
// PATTERN 2: Function returning deserialized value - use DeserializeOwned
fn parse<T: DeserializeOwned>(json: &str) -> Result<T, serde_json::Error> {
serde_json::from_str(json)
// json goes out of scope, T must own data
}
// PATTERN 3: Zero-copy parsing - use Deserialize<'de>
fn parse_zero_copy<'de, T: Deserialize<'de>>(json: &'de str) -> Result<T, serde_json::Error> {
serde_json::from_str(json)
// T can borrow from json, lifetime tied
}
// ERROR: Trying to return borrowed type without lifetime
// fn bad_parse<T: Deserialize<'de>>(json: &str) -> T {
// serde_json::from_str(json).unwrap()
// // Error: T might borrow from json, but json is dropped
// }
// ERROR: Trying to store borrowed type
// struct BadCache<'a, T: Deserialize<'a>> {
// data: HashMap<String, T>,
// // Error: T borrows from 'a, but HashMap owns T
// }
fn main() {
// Working examples
let cache: Cache<String> = Cache { data: HashMap::new() };
let parsed: String = parse(r#""hello""#).unwrap();
let json = r#""borrowed""#;
let borrowed: &str = parse_zero_copy(json).unwrap();
// borrowed points into json
}Common errors come from mixing borrowing types with owning contexts.
use serde::Deserialize;
use serde_json::Value;
fn main() {
// serde_json::from_str can produce borrowed content
let json = r#"{"key": "value"}"#;
// Value can borrow
let value: Value = serde_json::from_str(json).unwrap();
// But Value::String is actually owned inside
// serde_json::Value doesn't do zero-copy
// String always owns
let s: String = serde_json::from_str(r#""hello""#).unwrap();
// &str borrows from input
let json = r#""hello""#;
let s: &str = serde_json::from_str(json).unwrap();
// s points into json's buffer
// This demonstrates Deserialize<'de> allowing borrow
// String: for<'de> Deserialize<'de> (always owns)
// &str: Deserialize<'de> for specific 'de (borrows)
}Different types have different ownership characteristics.
use serde::{Deserialize, de::DeserializeOwned};
use std::collections::HashMap;
// Zero-copy parsing for large responses
#[derive(Deserialize)]
struct ApiResponse<'a> {
status: &'a str,
#[serde(borrow)]
data: Vec<ItemRef<'a>>,
}
#[derive(Deserialize)]
struct ItemRef<'a> {
#[serde(borrow)]
id: &'a str,
#[serde(borrow)]
name: &'a str,
}
// Owned version for storage
#[derive(Deserialize, Clone)]
struct OwnedApiResponse {
status: String,
data: Vec<OwnedItem>,
}
#[derive(Deserialize, Clone)]
struct OwnedItem {
id: String,
name: String,
}
struct ApiCache {
responses: HashMap<String, OwnedApiResponse>,
}
impl ApiCache {
// Must use DeserializeOwned for storage
fn store(&mut self, key: String, json: &str) {
let response: OwnedApiResponse = serde_json::from_str(json).unwrap();
self.responses.insert(key, response);
}
fn get(&self, key: &str) -> Option<&OwnedApiResponse> {
self.responses.get(key)
}
}
fn main() {
// Zero-copy for transient processing
let json = r#"{"status":"ok","data":[{"id":"1","name":"A"},{"id":"2","name":"B"}]}"#;
let response: ApiResponse = serde_json::from_str(json).unwrap();
// Process without allocations
for item in &response.data {
println!("ID: {}, Name: {}", item.id, item.name);
}
// Owned for storage
let mut cache = ApiCache { responses: HashMap::new() };
cache.store("key".to_string(), json);
}Use zero-copy for transient data, owned for storage.
Core difference:
Deserialize<'de>: Can borrow data with lifetime 'de from inputDeserializeOwned: for<'de> Deserialize<'de>, owns all data, no lifetime constraintsType signatures:
Deserialize<'de>: Propagates lifetime constraint to callersDeserializeOwned: No lifetime in signature, simpler APIZero-copy capability:
Deserialize<'de>: Enables zero-copy deserialization (strings reference input)DeserializeOwned: Always allocates owned dataWhen to use Deserialize<'de>:
&str or &[u8] with borrowed fieldsWhen to use DeserializeOwned:
from_reader)Key insight: The choice is about ownership and lifetime propagation. Deserialize<'de> is more flexible (can borrow or own) but propagates lifetime constraints through your API. DeserializeOwned is simpler (no lifetimes) but requires types to own all their data. For most application code where you're storing results, DeserializeOwned is the right choice. For performance-critical parsing of large inputs where you can process results before the input buffer goes away, Deserialize<'de> enables zero-copy optimization.