Product types
Records are Ship's product types. A record has a name and a fixed set of fields. The type is nominal: User is not just "something with id and name".
type User = {
id: Int;
name: String;
}
Construct a record with its type name. Field access reads stored data.
let adaada: Book.Records.User = User { id = 11: Int; name = "Ada""Ada": String; };
Construction
expect ✓adaada: Book.Records.User.idid: Int == 11: Int;
expect ✓adaada: Book.Records.User.namename: String == "Ada""Ada": String;Immutability
Values and fields are immutable by default. Building another record does not change the one you already have. See updating records for { value with ... } and mut fields, and mutability for let mut and :=.
let gracegrace: Book.Records.User = User { id = adaada: Book.Records.User.idid: Int; name = "Grace""Grace": String; };
Immutable By Default
expect ✓adaada: Book.Records.User.namename: String == "Ada""Ada": String;
expect ✓gracegrace: Book.Records.User.namename: String == "Grace""Grace": String;Synthesized behaviors
Ordinary concrete records get schema metadata and canonical Inspect, Display, Encode, and Decode. Opt out with !Inspect, !Display, !Encode, or !Decode.
Synthesized Behaviors
expect ✓Inspect.inspectinspect: pure fn!('a: Inspect)(value: Book.Records.User) -> String(adaada: Book.Records.User) == "User { id = 1; name = \"Ada\" }""User { id = 1; name = \"Ada\" }": String;
expect ✓Display.displaydisplay: pure fn!('a: Display)(value: Book.Records.User) -> String(adaada: Book.Records.User) == "User { id = 1; name = Ada }""User { id = 1; name = Ada }": String;
expect ✓must String.of_bytesof_bytes: pure fn(bytes: Bytes) -> String, exn [ :utf8(Int) ](Json.encodeencode: pure fn('a: Encode)(value: Book.Records.User) -> Bytes, exn [ :encode(Wire.EncodeError) ](adaada: Book.Records.User)) == "{\"id\":1,\"name\":\"Ada\"}""{\"id\":1,\"name\":\"Ada\"}": String;
let decodeddecoded: Book.Records.User = must Json.decodedecode: pure fn('target: Decode)(body: Bytes, target: Struct(Book.Records.User), ~max_depth: Int = 128) -> Book.Records.User, exn [ :decode(Wire.DecodeError) ](Json.encodeencode: pure fn('a: Encode)(value: Book.Records.User) -> Bytes, exn [ :encode(Wire.EncodeError) ](adaada: Book.Records.User), User);
expect ✓decodeddecoded: Book.Records.User.idid: Int == 11: Int;
expect ✓decodeddecoded: Book.Records.User.namename: String == "Ada""Ada": String;type PrivateNote !Encode !Decode = {
text: String;
}
Opt Out
let notenote: Book.Records.PrivateNote = PrivateNote { text = "draft""draft": String; };
expect ✓Inspect.inspectinspect: pure fn!('a: Inspect)(value: Book.Records.PrivateNote) -> String(notenote: Book.Records.PrivateNote) == "PrivateNote { text = \"draft\" }""PrivateNote { text = \"draft\" }": String;Construction may omit fields on a sparse record. Use record projection to materialize a focused nominal boundary type from selected fields, and record spreads to build a record type from the declared fields of others.