Ship

Book · Records

Records

Draft

Name a fixed set of fields as a product type.

On this page · 3 sections

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

test✓
  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

test✓
  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

test✓
  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

test✓
  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.