Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Type system

Statically sized types

Statically sized types are those stored with static (immutable) and fixed amount of contiguous space. The amount of space occupied solely depends on the type and not the value represented.

Boolean type

Booleans can represent one of two distinct values: true or false. Each boolean occupies 8 bits.

#![allow(unused)]
fn main() {
let rain: bool = true;
let sunny: bool = false;
}

Numeric types

Numeric types represents numbers. A numeric type must be declared with a character (representing the type of number) and a number (representing the size of the variable)

#![allow(unused)]
fn main() {
let number: cXY = ... // c = the char of the type
                       // XY = the size to allocate
}

Unsigned types represents absolute numbers (which, by convention are treated as positive numbers). Unsigned numbers are declared with the letter u. The following table shows the possible sizes.

TypeByte Size (bit)Min ValueMax Value
u81 byte (8 bits)\(0\)\(2^{8}-1\)
u162 bytes (16 bits)\(0\)\(2^{16}-1\)
u324 bytes (32 bits)\(0\)\(2^{32}-1\)
u648 bytes (64 bits)\(0\)\(2^{64}-1\)
u12816 bytes (128 bits)\(0\)\(2^{128}-1\)
#![allow(unused)]
fn main() {
let x1: u8 = 10;
let x2: u8 = 300; // compiler error. The variable type has not enough size to represent the value
}

Integer types represents integer numbers, both positive and negative. Integer numbers are declared with the letter i. The following table shows the possible sizes.

TypeByte Size (bit)Min ValueMax Value
i81 byte (8 bits)\(-2^{7}\)\(2^{7}-1\)
i162 bytes (16 bits)\(-2^{15}\)\(2^{15}-1\)
i324 bytes (32 bits)\(-2^{31}\)\(2^{31}-1\)
i648 bytes (64 bits)\(-2^{63}\)\(2^{63}-1\)
i12816 bytes (128 bits)\(-2^{127}\)\(2^{127}-1\)

Float types represent non integer numerals (both positive and negative) following the IEEE 754 standard. Float numbers are declared with the letter f. The only possible sizes are 32 or 64 bits. Thus the two possible types are f32 (single precision) and f64 (double precision).

TypeByte Size (bit)Min Value (Normalized)Min Value (Subnormal)Max Value
f324 byte (32 bits)\(1.0×2^{−126}\)\(2^{−149}\)\((2−2^{−23})×2^{127}\)
f648 bytes (64 bits)\(1.0×2^{−1022}\)\(2^{−1074}\)\((2−2^{−52})×2^{1023}\)
#![allow(unused)]
fn main() {
let x: f32 = 10.5;
let y: f64 = 10.5f;
}

As seen in the example both the declarations are valid.

The usize type is an unsigned integer type with the same number of bits as the platform’s pointer type. It can represent every memory address in the process.

The isize type is a signed two’s complement integer type with the same number of bits as the platform’s pointer type. The theoretical upper bound on object and array size is the maximum isize value. Thus isize can be used to calculate differences between pointers into an object or array and can address every byte within an object along with one byte past the end.

Both usize and isize are at least 16 bit lenght.

Character

A character represents a Unicode scalar value. Characters are declared as char and the declared value must be enclosed in single quotes. Every character is stored in 4 bytes (32 bit), thus a sort of alias to u32 type.

#![allow(unused)]
fn main() {
let c: char = 'a';
let emoji: char = '😀';
let unicode: char = '\u{1F600}';
}

Pointer types

Note

This is an example note.

Compound data types

The following types still have a static (immutable) amount of space allocated but it depends both on the types and the amount of values to store.

Textual types

Textual types represents strings, thus sequences of characters.

Strings are declared as strand the value enclosed in double quotes.

#![allow(unused)]
fn main() {
let say: str = "Hello";
}

C-strings are a sequence of characters (non unicode encoded) terminated by a null character (‘\0’). They are identified by prefixing the literal declaration with the qualifier @c.

#![allow(unused)]
fn main() {
let say: str = @c"Hello";
}

Array types

Arrays represent 1D homogeneous product types. They are a statically sized, contiguous blocks of memory containing elements of a single type T.

The syntax for an array type is [T; SIZE].

#![allow(unused)]
fn main() {
let arr: [i32; 3] = [1, 2, 3];
}

Tensor types

Tensors represent multi-dimensional (nD) homogeneous product types. Like arrays, they are statically sized collections of a single type T, but structured across multiple dimensions.

The syntax for a tensor type separates the dimensions with commas: [T; SIZE1, SIZE2, ...].

#![allow(unused)]
fn main() {
let matrix: [f32; 2, 2] = [[1.0, 0.0], [0.0, 1.0]];
}

Note

Compound types like arrays and tensors are not “objects”. Dynamic, heap-allocated collections (such as vectors or dynamic tensors) are managed through the standard library or via gradual typing features, rather than being built into the static compound type syntax.

Tuple types

Tuples are Cartesian product types. They are ordered, statically sized, heterogeneous collections of values where each element can be of a different type.

The syntax for a tuple type uses parentheses () containing a comma-separated list of types: (T, U, V).

#![allow(unused)]
fn main() {
let record: (i32, f64, str) = (42, 3.14, "hello");
}

Struct types

Structs are heterogeneous product of other types (called fields). Structs must be declarated with a name to refer to and the type and the name for eah of it’s fields.

#![allow(unused)]
fn main() {
struct AName {
  // fields of AName
  x: i32,
  y: f64,
}
let aStruct = AName ( x: 15, y: 10.4 );
}

Structs fields can be accessed with . followed by the field name.

#![allow(unused)]
fn main() {
let aVar : i32 = aStruct.x; // 15
}

Unit-like structs are structs with no fields. Those structs can be initialized with only the name.

#![allow(unused)]
fn main() {
struct AStruct{}    // Unit-like struct declaration

let a: AStruct;               // AStruct variable initialization
let b: AStruct = AStruct{};   // Equivalent initialization
}

Enum types

Enum is a type which defines a new enumerated type domain. Each Enum is declared with a name (like for structs) and the allowed values. Enum constructors (or variants) are assignable to variables using the Enum name as the type of the variable.

Each variant can be declared with just name to refer to (Unit-like) or have the same syntax of structs, tuple or unions.

#![allow(unused)]
fn main() {
enum Animals{
  Dog( str, i32 ),
  Cat{ name: str, age: i32 },
  Spider{ eyes: i32, poisonous: bool },
  Reptile,
}

let a: Animal = Animal::Spider{ eyes: 8, poisonous: false };
let b: Animal = Animal::Reptile;
}

Variants defined inside Enum declaration cannot be used as a type specifier.

#![allow(unused)]
fn main() {
let b: Cat = Animals::Cat{..}  // Compiler error. Cat not defined
let c: Animals::Cat = Animals::Cat{..}  // Another compiler error
}

A constructor with no fields is called Unit-Like. When all the constructors in an enum are Unit-Like, then the enum is called Unit-Only Enum (or Field-less).

#![allow(unused)]
fn main() {
enum Balls{
  Tennis,
  Golf,
  Soccer,
}

let a: Balls = Balls::Tennis 
}

Each Enum instance has an associated dicriminant, an integer (isize) that determines which variant of the enum it holds. A discriminant value can be assigned to only one variant and a variant can have only one discriminant. Discriminants can be manually assigned in Enum declaration as it follows:

#![allow(unused)]
fn main() {
enum Balls{
  Tennis = 4,
  Golf = 1,
  Soccer = 2,
}
}

Non specified discriminant are automatically assigned as the discriminant of the previous constructor in the declaration increased by 1. (If it’s the first constructor then it’s set to 0)

#![allow(unused)]
fn main() {
enum Balls{
  Tennis,     // Unspecified discriminant for first variant. set to 0
  Golf = 10,
  Soccer,     // Discriminant will be 11
}
}

Discriminant of a variant can be accessed casting the enum to an isize.

#![allow(unused)]
fn main() {
let a: Balls = Balls::Golf;
let discr: isize = a as isize // discr contains 10
}

Static types layout

Dinamically sized types


Union types

Note

This is an example note.

Union types (also known as sum types) can store different types of values but only one at a time. Unions are declared similar to structs but with use of the union keyword (comma separated).

#![allow(unused)]
fn main() {
union MyUnion {
    f1: u32,
    f2: f32,
}
}

An example of two identically declared Unions with differently stored values.

View types