Starlark Types
The Starlark 'types' extension is highly experimental and likely to be modified in the future.
Types can be added to function arguments, or function return types.
For example:
def fib(i: int) -> int:
...
There are moments where types can be checked:
- At runtime, as a function is executed, when a value of the appropriate type is available.
- Statically, without executing anything.
- At compile time, when the definitions of all symbols imported using
loadare available.
Currently runtime is the normal way of checking, but other systems built on Starlark (e.g. Buck2) may also perform additional types of checking. In all cases the meaning of the types is the same.
The rest of this document lays out what types mean and what type-supporting values are available (records and enums).
What does a type mean?
A type is a Starlark expression that has a meaning as a type:
- When
fib(3)is called, the value3is passed tofibas parameteri. - When the execution of
fibis started, the expressionintis evaluated to the value of theintfunction. - A check is then made that the value
3matches the type represented byint.
If the value doesn't match, it is a runtime error. Similarly, on return
statements, or the end of the function, a check is made that result type matches
int.
As some examples of types:
- The type
typing.Anymatches any value, with no restrictions. - The types
int,bool,strall represent the values produced by the respective functions. - The type
Nonerepresents the valueNone. - The type
list[int]represents a list ofinttypes, e.g.list[typing.Any]represents a list containing any types. - The type
dict[int, bool]represents a dictionary withintkeys andboolvalues. - The type
tuple[int, bool, str]represents a tuple of arity 3 with components beingint,boolandstr. - The type
tuple[int, ...]represents a tuple of unknown arity where all the components are of typeint. - The type
int | boolrepresents a value that is either anintor abool. - The type
typing.Callablerepresents something that can be called as a function. - The type
typing.Iterablerepresents something that can be iterated on. - The type
typing.Neverrepresents a type with no valid values - e.g. the result offailistyping.Neveras the return value offailcan never be observed, given the program terminates.
The goals of this type system are:
- Reuse the existing machinery of Starlark as much as possible, avoiding inventing a special class of type values. As a consequence, any optimisations for values like string/list are reused.
- Provide a pleasing syntax.
- Some degree of compatibility with Python, which allows types as expressions in the same places Buck2 allows them (but with different meaning and different checking).
- And finally, a non-goal is to provide a complete type system capable of representing every type invariant: it's intended to be a lossy approximation.
In addition to these built-in types, records and enumerations are provided as special concepts.
Record types
A record type represents a set of named values, each with their own type.
For example:
MyRecord = record(host=str, port=int)
This above statement defines a record MyRecord with 2 fields, the first named
host that must be of type str, and the second named port that must be of
type int.
Now MyRecord is defined, it's possible to do the following:
- Create values of this type with
MyRecord(host="localhost", port=80). It is a runtime error if any arguments are missed, of the wrong type, or if any unexpected arguments are given. - Get the type of the record suitable for a type annotation with
MyRecord. - Get the fields of the record. For example,
v = MyRecord(host="localhost", port=80)will providev.host == "localhost"andv.port == 80. Similarly,dir(v) == ["host", "port"].
It is also possible to specify default values for parameters using the field
function.
For example:
MyRecord = record(host=str, port=field(int, 80))
Now the port field can be omitted, defaulting to 80 is not present (for
example, MyRecord(host="localhost").port == 80).
Records are stored deduplicating their field names, making them more memory efficient than dictionaries.
Enum types
The enum type represents one value picked from a set of values.
For example:
MyEnum = enum("option1", "option2", "option3")
This statement defines an enumeration MyEnum that consists of the three values
"option1", "option2" and "option3".
Now MyEnum is defined, it's possible to do the following:
- Create values of this type with
MyEnum("option2"). It is a runtime error if the argument is not one of the predeclared values of the enumeration. - Get the type of the enum suitable for a type annotation with
MyEnum. - Given a value of the enum (for example,
v = MyEnum("option2")), get the underlying valuev.value == "option2"or the index in the enumerationv.index == 1. - Get a list of the values that make up the array with
MyEnum.values() == ["option1", "option2", "option3"]. - Treat
MyEnuma bit like an array, withlen(MyEnum) == 3,MyEnum[1] == MyEnum("option2")and iteration over enums[x.value for x in MyEnum] == ["option1", "option2", "option3"].
Enumeration types store each value once, which are then efficiently referenced by enumeration values.