Records

A record is a collection of privacy-enforced typed data elements grouped under one name. For instance, we can define a Person record with the following code:

record Person {
  public string name;
  private int age;
  private double balance;
}

The data elements mirror how data is spelled out within the root document; however, a record can be used in multiple ways. For example, a record can be held within another record.

record Relationship {
  public Person a;
  public Person b;
}

This simple re-use becomes the foundation for building composite types and collections.

See:

  • types for more information on which types can go within records.
  • privacy policies for how privacy per field is specified within a record.
  • bubbles for how data is exposed based on the viewer from the record.
  • reactive formulas for how to expose reactively compute data to the viewer of record.

Records also have methods, can declare privacy policies, and hint at indexing for tables. If a record is used within a table, an implicit field called id is created with integer type. Since communication is done with messages, conversion to a message is provided as a free helper.

Methods

We can associate code to a record via a method. For example, a method may mutate a record which is useful for consolidating how records change.

record R {
  public int score;
  
  method zero() {
    score = 0;
  }
}

Methods can be marked as read-only such that they are not allowed to mutate the document and thus become available for reactive formulas.

record R {
  public int score;
  
  method double_score() -> int readonly {
    return score * 2;
  }

  public formula ds = double_score();
}

Policies

Records can express policies which are bits of code associated to the record along with @who.

record R {
  private principal owner;
  
  policy is_owner {
    return owner == @who;
  }
  
}

A policy can be used to protect fields within a record.

record R {
  private principal owner;
  
  use_policy<is_owner> int balance;
  
  policy is_owner {
    return owner == @who;
  }
  
}

Alternatively, a policy may be used to protect the entire record.

record R {
  private principal owner;
  
  public int balance;

  policy is_owner {
    return owner == @who;
  }
 
  require is_owner;
}

Indexing tables

The best mental model for a record is a row within a table. By default, a row has a primary key index on id which has a type of int. Additional fields within a record can be indexed to speed up queries.

record R {
  private int key;
  
  index key;
}

table<R> _table;

The index keyword will inform _table that it can group records by the key field to reduce the number of candiates considered during a where clause. This will introduce both memory and computational overhead to maintain.

Easily convert to a message for communication

Given a message or record, we can convert it into a message type via the @convert keyword.

record R {
  public int x;
}
R r;

message M {
  int x;
}

#sm {
 M m = @convert<M>(r);
}

This conversion is useful with channels and futures are outlined as we sometimes want to present people with a list of options derived from a table. It is also useful for sending records to another service.