// 768. Zero-Copy Deserialisation with Lifetime Tricks
// Borrows &'de str from input โ zero heap allocation
// โโ Zero-copy record: fields borrow from input โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
/// 'de = "deserialize" lifetime โ the input buffer must outlive this struct
#[derive(Debug)]
pub struct PersonView<'de> {
pub name: &'de str,
pub age_raw: &'de str, // raw string, parse lazily
pub city: Option<&'de str>,
}
impl<'de> PersonView<'de> {
pub fn age(&self) -> Option<u32> {
self.age_raw.parse().ok()
}
}
// โโ Simple zero-copy parser โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
#[derive(Debug)]
pub struct ParseError(String);
/// Parse "name=Alice|age=30|city=Berlin" without any allocation
pub fn parse_view(input: &str) -> Result<PersonView<'_>, ParseError> {
// Returns &str slices that borrow from `input`
fn find_field<'a>(input: &'a str, key: &str) -> Option<&'a str> {
let prefix = format!("{key}=");
for part in input.split('|') {
if let Some(v) = part.strip_prefix(prefix.as_str()) {
return Some(v);
}
}
None
}
let name = find_field(input, "name")
.ok_or_else(|| ParseError("missing 'name'".into()))?;
let age_raw = find_field(input, "age")
.ok_or_else(|| ParseError("missing 'age'".into()))?;
let city = find_field(input, "city");
Ok(PersonView { name, age_raw, city })
}
// โโ Owned version (comparison) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
#[derive(Debug)]
pub struct PersonOwned {
pub name: String,
pub age: u32,
pub city: Option<String>,
}
impl<'de> From<PersonView<'de>> for PersonOwned {
fn from(v: PersonView<'de>) -> Self {
PersonOwned {
name: v.name.to_string(),
age: v.age().unwrap_or(0),
city: v.city.map(|s| s.to_string()),
}
}
}
// โโ Lifetime demo: showing 'de in action โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
/// This function signature shows how 'de ties input lifetime to output lifetime
pub fn deserialize_person<'de>(input: &'de str) -> Result<PersonView<'de>, ParseError> {
parse_view(input)
}
// โโ Batch zero-copy parsing of many records โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
pub fn parse_many(input: &str) -> Vec<PersonView<'_>> {
input.lines()
.filter(|l| !l.is_empty())
.filter_map(|line| parse_view(line).ok())
.collect()
}
fn main() {
let input = "name=Alice|age=30|city=Amsterdam";
let view = parse_view(input).expect("parse failed");
println!("Name: {}", view.name); // &str pointing into `input`
println!("Age : {:?}", view.age());
println!("City: {:?}", view.city);
// Zero-copy batch
let records = "name=Bob|age=25\nname=Carol|age=35|city=Berlin\nname=Dave|age=40";
let views = parse_many(records);
println!("\nBatch ({} records):", views.len());
for v in &views {
println!(" {}: age={}", v.name, v.age_raw);
}
// Convert to owned when needed
let owned: Vec<PersonOwned> = views.into_iter().map(PersonOwned::from).collect();
println!("\nOwned: {:?}", owned.iter().map(|o| &o.name).collect::<Vec<_>>());
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn zero_copy_name_borrows_input() {
let input = String::from("name=Umur|age=33");
let view = parse_view(&input).unwrap();
// view.name is a slice of `input` โ no allocation
assert_eq!(view.name, "Umur");
assert_eq!(view.age(), Some(33));
}
#[test]
fn optional_city() {
let input = "name=Eve|age=28|city=Paris";
let view = parse_view(input).unwrap();
assert_eq!(view.city, Some("Paris"));
}
#[test]
fn missing_city_is_none() {
let view = parse_view("name=X|age=1").unwrap();
assert!(view.city.is_none());
}
#[test]
fn missing_field_errors() {
assert!(parse_view("age=30").is_err());
}
}
(* Zero-copy style in OCaml โ using substrings that share the underlying buffer
Note: OCaml strings are immutable; Bytes.sub_string still copies.
We simulate zero-copy with offset+length pairs. *)
type string_view = { src: string; off: int; len: int }
let view_to_string sv = String.sub sv.src sv.off sv.len
(* A "zero-copy" record โ fields are views into the input buffer *)
type person_view = {
name: string_view;
age_str: string_view; (* raw string, parse lazily *)
}
(* Parse "name=Alice|age=30" without copying field contents *)
let parse_view (s: string) : person_view option =
let find_char s start c =
let rec go i = if i >= String.length s then None
else if s.[i] = c then Some i
else go (i+1)
in go start
in
(* find first '|' *)
match find_char s 0 '|' with
| None -> None
| Some pipe ->
(* name field: "name=Alice" โ from 5 to pipe *)
let name_off = 5 in (* skip "name=" *)
let name_len = pipe - name_off in
(* age field: skip "age=" after pipe *)
let age_off = pipe + 1 + 4 in (* skip "age=" *)
let age_len = String.length s - age_off in
if name_len <= 0 || age_len <= 0 then None
else Some {
name = { src = s; off = name_off; len = name_len };
age_str = { src = s; off = age_off; len = age_len };
}
let () =
let input = "name=Alice|age=30" in
match parse_view input with
| None -> Printf.printf "parse failed\n"
| Some pv ->
Printf.printf "Name (view): %s\n" (view_to_string pv.name);
Printf.printf "Age (view): %s\n" (view_to_string pv.age_str);
Printf.printf "Age (int) : %d\n"
(int_of_string (view_to_string pv.age_str))