Types
Primitives:
| Group | Types |
|---|---|
| signed integers | s8 s16 s32 s64 s128 |
| unsigned integers | u8 u16 u32 u64 u128 |
| floats | f32 f64 |
| boolean | bool |
| text | string, char (see below) |
Plus any struct / enum name (scoped: pkg::module::Type), arrays (Type[],
Type[max 100] or fixed Type[10]), and unions (union(TypeA TypeB)).
Every string and every dynamic array needs a maximum, written in its
type or given by a setting. The examples in this guide
carry maxima so they build as written; a bare string or T[] in one of your
schemas builds only once a default is set.
Text: string and char
charis one Unicode scalar value: a single character, not a byte or a UTF-16 unit. Its literal is a string of exactly one character,"a"; any other length is a build error.stringis the text type: UTF-8, the built-in name forchar[]. An array ofcharis always text, sochar[max 64]is a string of at most 64 characters, not a list of 64charvalues.strwas an accepted spelling until it was removed. Writing it is now a build error that tells you to usestring.String(capitalised) is not Comline syntax. It is the Rust type thatstringgenerates into, so you see it in generated Rust, never in a.idsfile. Writing it gets the same hint.
Bounds
A maximum says how big a value may be, in its type, so a decoder can refuse
an oversized value before it allocates one. It is written max N inside the
brackets, and counts items, or characters for a string:
struct Customer {
name: char[max 32] // a string of at most 32 characters
code: char[8] // a string of exactly 8 characters
orders: Order[max 100] // at most 100 orders
tags: char[max 32][max 10] // at most 10 tags, each at most 32 characters
ids: u64[16] // exactly 16 (a fixed array, as always)
}
T[N]is exactlyN.T[max N]is at mostN.T[]writes no maximum, and needs one from a setting.- Only a maximum. A minimum ("not empty", "at least one") is a
validator:
NonEmpty,MinChars,MinItems. So is a range of values,IntRange. Nis an integer or a constant:char[max NAME_MAX]. It is at least 1 and at mostu32::MAX(4294967295);max 0, an exact length of 0 and a negative size are build errors.maxis a keyword only right after[. Anywhere else it is an ordinary name, so a field calledmaxis fine.- Only strings and dynamic arrays take one. Integers, floats,
bool, enums, structs, unions and()don't.string[max 10]is ten strings, each at the package default; for a bounded string writechar[max N]. - Any change to a maximum is breaking: tightening breaks old senders and loosening breaks old receivers.
Every string and list has a maximum
A schema builds only when every string and dynamic array, at every position (field, argument, return, alias, union member), has a maximum, from the first of:
- the position itself,
char[max 64]; - a default from settings,
bounds.string.maxfor strings andbounds.array.maxfor lists, stored resolved at each position it reaches; - a give-up:
@unverifiedon every protocol that reaches the position, which gives up bounded size.
There is no built-in default, so a position with none of these is an error that names it and the fix:
struct `Greeting`, field `message`: `string` has no maximum
Help: write `char[max N]`, or set `bounds.string.max` in settings
A position no protocol reaches, such as a type another package can use, has to be bounded. A struct that can contain itself also needs a depth limit.
A package default reaches only its own positions: a dependency's types keep the maxima resolved in the dependency.
Using a bounded alias
A type alias carries its bound, and the limit is changed
there. Where an alias of a string or of a dynamic array is used (a field,
argument, return or union member), it takes no […] suffix of its own:
type Name = char[max 32]
type Names = Name[max 10] // a list of an alias is its own alias
struct Customer {
name: Name // fine
nicknames: Names // fine
others: Name[max 5] // error: `Name` is an alias of a string, which
// takes no `[…]` where it's used
}
The error suggests the alias to declare instead. The rule covers aliases of
strings and dynamic arrays; an alias of a number or a struct keeps the suffix
(UserId[max 10]), and the built-in string is exempt (string[max 10]).
Writing an alias in a list inside another alias, as Names does, is allowed.
Type aliases
Writting type NAME = TYPE gives a type a second name:
type UserId = u64
type Handle = char[max 32]
struct User {
id: UserId
handle: Handle
}
An alias is transparent: UserId is a u64 everywhere, in the wire format and in generated code. (In Rust is: pub type UserId = u64;, in Typescript being: export type UserId = number; etc).
An alias can name another alias.
An alias can carry Validators, which then check every value of it.
In generated code
| Comline | Rust | TypeScript |
|---|---|---|
s8 … s32 |
i8 … i32 |
number |
u8 … u32 |
u8 … u32 |
number |
s64, s128 |
i64, i128 |
bigint |
u64, u128 |
u64, u128 |
bigint |
f32 / f64 |
f32 / f64 |
number |
bool |
bool |
boolean |
string, char[max N] |
String |
string |
char |
not supported yet | not supported yet |
Message |
Message |
Message |
Message[] |
Vec<Message> |
Message[] |
u8[16] |
[u8; 16] |
number[] |
union(string u32) |
not supported yet | string \| number |
optional x: T |
Option<T> |
x?: T |
A maximum is in the stored schema and part of a package's version, but
generated code doesn't enforce it yet: a bounded string is a String or
string, and a bounded list a Vec or an array. Checks while decoding and
encoding come with the runtimes, and both generators refuse a single char
until a wire format carries one. Each generator's state is on the
language support page.
TypeScript's number holds integers exactly only up to 2^53, so the 64- and
128-bit integers are bigint there, in generated types and in both codecs.
Fixed arrays
A fixed array keeps its length: in Rust it's always [T; N]. TypeScript has
no fixed-length array short of a tuple, so there it's T[], and the length
isn't checked. Fixed and dynamic arrays encode the same way on the wire.
Rust's serde derive only handles arrays up to 32 elements. Longer ones go
through serde-big-array:
- As a struct or error field's own type (
hash: u8[64]), the field gets#[serde(with = "serde_big_array::BigArray")]. Inlibmode the generatedCargo.tomlalready hasserde-big-array = "0.5". Incodemode the file starts with a comment naming that line for you to add. - Anywhere else, the Rust generator refuses it with an error naming the spot:
an
optionalfield, an element of another array, a function argument or return, atypealias target. Wrap the array in a struct field instead.
Changing a fixed array's length (u8[4] → u8[8]) is a breaking change, the
same as changing f32 to f64.
Unions
TypeScript renders a union as A | B. The Rust generator doesn't render
unions yet: it stops with an error naming the field, argument or return that
uses one, rather than generating code that won't compile
(comline-rust#51).