Chapter 1: Connect One Type Family by Hand
An i32 can be copied out of an array. A String should be read as an &str that borrows the
array. This chapter connects both cases without forcing one into the other’s ownership model.
Prerequisites: enums, traits, references, associated types, and Option.
By the end of this chapter, you will:
- connect owned values, borrowed values, arrays, and builders for
i32andString; - use a generic associated type for the borrowed member of each family; and
- upcast typed values with
Fromand downcast erased enums with checkedTryFromconversions.
See the missing connections
Copy the cumulative contract and run it once:
cargo x copy-test --chapter 1
cargo test -p type-exercise-starter chapter_1 --locked
The untouched starter compiles because it declares the Day 1 target shapes, but the focused test
should fail at the Day 1 todo! boundary. It should not fail on an unresolved target. Do not edit
the copied test.
The family you are building has reciprocal arrows:
Scalar ──RefType<'a>──> ScalarRef<'a>
│ │
ArrayType ArrayType
▼ ▼
Array ─────Builder─────> ArrayBuilder
For i32, the borrowed scalar is another i32. For String, it is &'a str. Array::get
therefore returns Option<i32> for I32Array and Option<&str> for StringArray without an
allocation on the string read.
Checkpoint 1: describe the two physical families
- Target:
type-exercise-starter/src/physical_type.rs::{PhysicalType, TypeMismatch}andtype-exercise-starter/src/scalar.rs::{Scalar, ScalarRef, ScalarImpl, ScalarRefImpl}. - Change: add only the
Int32andStringphysical rows and their reciprocal associated types. - Preserve: the original two
ScalarImplvariants and safe Rust. - Run: the Chapter 1 focused test.
- Passing means: owned and borrowed values point to the correct array family.
for<'a> on a bound means the relationship holds for every caller-chosen borrow lifetime. The
integer implementation may ignore that lifetime; the string implementation cannot.
Checkpoint 2: store nullable rows
- Target:
type-exercise-starter/src/array.rs::{Array, ArrayBuilder, ArrayImpl},type-exercise-starter/src/array/primitive_array.rs::{PrimitiveArray, PrimitiveArrayBuilder}, andtype-exercise-starter/src/array/string_array.rs::{StringArray, StringArrayBuilder}. - Change: implement
get,len,iter,from_slice, builderpush, andfinishfor the two families. Expose read-onlyvalues/validityaccessors onPrimitiveArrayanddata/offsets/validityaccessors onStringArrayso the buffer contract is visible. - Preserve: row count and null positions; returned strings must borrow array storage; offsets count UTF-8 bytes rather than characters.
- Run: the same focused test.
- Passing means: normal, null, and empty arrays read through one generic contract.
Use the Arrow-like layout required by the supplied test. A fixed-width array stores one flat
Vec<T> with exactly one slot per row. A string array stores all UTF-8 bytes in one flat Vec<u8>
and uses rows + 1 monotone offsets: the first is zero, the last is the byte-buffer length, and a
null or empty row repeats an offset. Both arrays store row validity in the packed
bitvec::vec::BitVec already declared in the starter. A null row returns None, but it is never an
Option<T> payload slot; strings are not stored as one owned String per row.
Checkpoint 3: erase and recover values
- Target:
From/TryFromimplementations intype-exercise-starter/src/scalar.rsandtype-exercise-starter/src/array.rs, plus exports intype-exercise-starter/src/lib.rs. - Change: upcast typed values into erased enums and recover the requested type.
- Preserve: wrong variants return
TypeMismatch { expected, actual }; they do not panic. - Run: the focused and cumulative starter tests.
- Passing means: correct variants round-trip and wrong variants fail at the boundary.
Required and extension work
Required work is exactly the explicit i32 and String rows. Additional physical types, macros,
columns, and expressions belong to later chapters. As an extension, sketch a third family on paper
and mark every enum arm and conversion it would require; do not implement it yet.
cargo test -p type-exercise-starter chapter_1 --locked
cargo test -p type-exercise-starter --lib --locked
Before continuing, explain why StringArray::get needs a lifetime-indexed associated type and why
an erased downcast is fallible even when the compile-time family is consistent.
Next: Chapter 2 scales the family without copying every connection.
Your feedback is greatly appreciated. Join our Discord Community. Found an issue? Create an issue or pull request.
© 2022-2026 Alex Chi Z. Licensed under CC BY-NC-SA 4.0.