Ownership: move & borrow
Here is the idea that makes Rust feel different — and the one that makes a data engine
fast. Every value has exactly one owner. Hand it to someone else and you’ve moved
it; you can’t use it anymore. Or lend it out with & — a borrow — and keep
ownership. Clinker leans hard on borrowing, and you can see why right in the code that
reads a field.
You’ll be able to: distinguish move, borrow, and clone, and explain why
FieldResolver hands out a borrow of a field rather than a copy.
The engine reads fields by borrowing
Section titled “The engine reads fields by borrowing”When CXL evaluates status == "active", it has to read the status field of the
record. The trait that resolves a field name to its value is FieldResolver:
clinker-record ·resolver.rs ·FieldResolver trait @47d2e12
pub trait FieldResolver { fn resolve(&self, name: &str) -> Option<&Value>; // ^^^^^^^^^^^^^^^^ // a BORROW of the value, not a copy}That return type — Option<&Value> — is the whole lesson. &Value is a reference: a
borrow of the value living inside the record. The resolver doesn’t hand back a Value
(which would mean copying it out); it hands back a window onto the one that’s already
there. CXL can read status, compare it, and move on without ever duplicating it.
(We’ll cover the Option part — what the None means — in the next lesson.)
What a clone would cost
Section titled “What a clone would cost”Why does this matter so much? Because copying a Value isn’t always cheap. A
Value::Integer is a few bytes, sure — but a Value::String owns its text and a
Value::Array owns a whole Vec. Cloning those allocates. Now multiply by every
field access, of every record, in a million-row file. Borrowing is free; cloning is a
tax. So the engine’s rule is: borrow to read, and only pay for a clone at the one spot
that genuinely needs to keep a value (a topic we finish in the zero-copy lesson, 2.8).
Move, borrow, clone — see all three
Section titled “Move, borrow, clone — see all three”> output appears here — press Run
Run it, then uncomment the read(&status) line after the move and watch the borrow
checker explain that status no longer holds anything.
// quick check
Why does FieldResolver::resolve return Option<&Value> instead of Option<Value>?
Returning &Value lends a window onto the value already in the record. Returning Value would copy it out, and copying a string or array allocates — a cost the engine refuses to pay on every field read.
Checkpoint
Section titled “Checkpoint”You understand why records flow on borrows. Next: the two types that encode “maybe
absent” and “maybe failed” — Option and Result — which you already half-met in that
Option<&Value>.