Skip to main content

yo_shape/
lib.rs

1//! The shape tag: what makes "type safe" mean something across six languages
2//! and across time (`15` section 3).
3//!
4//! A C ABI erases types and a file outlives every process that opens it, so
5//! neither the host language's generics nor the ABI can tell a Python program
6//! that the collection it just opened as a map of strings was written as a map
7//! of integers by a Go program last March. Something in the file has to know.
8//!
9//! That something is a **canonical description**: a byte string that says what
10//! the element type is, in a grammar no language owns, that every binding
11//! computes identically. Its first 128 bits of BLAKE3 are the **tag**, and the
12//! tag is what a collection stores and what an open compares.
13//!
14//! ```
15//! use yo_shape::{Desc, Shape, Tag};
16//!
17//! struct Order;
18//!
19//! impl Shape for Order {
20//!     fn describe(d: &mut Desc) {
21//!         d.strukt("Order", &[("id", u64::describe), ("total", f64::describe)]);
22//!     }
23//! }
24//!
25//! // Same shape, same tag, on every machine and in every language.
26//! assert_eq!(Tag::for_type::<Order>(), Desc::of::<Order>().tag());
27//! assert_eq!(Desc::of::<Order>().tag().to_string().len(), 32);
28//! ```
29//!
30//! # When the shapes differ
31//!
32//! A tag comparison alone would produce the worst error message a database can
33//! give: something moved, and nothing about what. So the description is stored
34//! next to the tag, and a mismatch renders both, underlines the difference,
35//! names it in a sentence, and says whether it is additive or breaking.
36//!
37//! ```
38//! use yo_shape::{Desc, Shape, check};
39//!
40//! let stored = Desc::of::<u32>();
41//! let opening = Desc::of::<u64>();
42//! let e = check("hits", &stored, &opening, None).unwrap_err();
43//! assert!(e.message().contains("the type changed from u32 to u64"));
44//! assert_eq!(e.detail(), Some("change=breaking"));
45//! ```
46//!
47//! # What is not here
48//!
49//! The catalogue. Where a tag is stored, the list of prior tags that makes an
50//! additive change open silently, and the creating SDK and version all belong
51//! to the file (`07` section 5) and arrive with it. This crate computes,
52//! compares and explains; it does not persist.
53//!
54//! The `#[derive(Yo)]` that writes [`Shape`] for you also comes later. Until
55//! then a handful of lines per type is the price, and writing one by hand is
56//! the best way to see that the description is not magic.
57
58#![deny(missing_docs)]
59
60pub mod desc;
61pub mod diff;
62pub mod parse;
63
64pub use desc::{Bytes, Desc, Describe, Metric, Prim, Shape, Tag};
65pub use diff::{Change, ChangeKind, Provenance, check, compare, mismatch};
66pub use parse::{Type, parse};