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

Introduction

Warning

This book is currently in the early stages of development.

This book is the primary reference for the Eter programming language. This book does not assume you are reading it sequentially. Each chapter generally can be read standalone, but will cross-link to other chapters for facets of the language they refer to, but do not discuss.

Note

For known bugs and omissions in this book, see our GitHub issues. If you see a case where the compiler behavior and the text here do not agree, file an issue so we can think about which is correct.

Contributing

We welcome contributions of all kinds.

You can contribute to this book by opening an issue or sending a pull request to the Eter repository. If this book does not answer your question, and you think its answer is in scope of it, please do not hesitate to file an issue. Knowing what people use this book for the most helps direct our attention to making those sections the best that they can be. And of course, if you see anything that is wrong or is non-normative but not specifically called out as such, please also file an issue.

Lexical structure

A sequence of Unicode characters is translated into a sequence of tokens. The following rules apply during translation:

  1. Comments are treated as though they are a single space U+20.
  2. Spaces are ignored unless they appear between the opening and closing delimiters of a character or string literal. Unicode characters with the “White_Space” property are recognized as spaces.
  3. New-line delimiters are ignored unless they appear between the opening and closing delimiters of a character or string literal. Unicode characters with the “Line_Break” property are recognized as new-line delimiters.

Comments

The character sequence // starts a single-line comment, which terminates immediately before the next new-line delimiter.

The character sequences /* and */ are multiline comment opening and closing delimiters, respectively. A multiline comment opening delimiter starts a comment that terminates immediately after a matching closing delimiter. Each opening delimiter must have a matching closing delimiter. Multiline comments may nest and need not contain any new-line characters.

Note

The character sequences // have no special meaning in a multiline comment. The character sequences /* and */ have no special meaning in a single-line comment. The character sequences // and /* have no special meaning in a string literal. String and character literal delimiters have no special meaning in a comment.

Tokens

A token is a terminal symbol of the syntactic grammar. It falls into one of five categories:

CategoryDescriptionExamples
LiteralsFixed values in the source code42, "hello", true
KeywordsReserved words with special meaningif, else, while
IdentifiersNames given to entities (variables, functions, etc.)foo, bar, count
OperatorsSymbols representing computations or logic+, -, *, ==
DelimitersSymbols used for grouping and structure(, ), {, }

Note

The input a << b is translated to a sequence of 4 tokens: identifier, raw-operator, raw-operator, identifier.

Unless otherwise specified, the token recognized at a given lexical position is the one having the longest possible sequence of characters.


Literals

Literals are tokens representing fixed values in the source code. The language supports the following types of literals:

Literal TypeDescriptionStandard ExamplesSpecial/Escaped Examples
IntegerDecimal and hexadecimal formats1230x1A, 0x_FF_00
Floating-PointLike an integer literal but includes a decimal point .3.14159, 0.5
BooleanRepresent truth valuestrue, false
CharacterSingle Unicode scalar value enclosed in single quotes'a', 'R''\n', '\t', '\'', '\u{1F600}'
StringSequence of characters enclosed in double quotes"hello world"r"C:\Path", r#"He said, "Hello!""#
C-Style StringNull-terminated string literal identified by the @c qualifier@c"hello"@c"c style string"

Keywords

Keywords are reserved words that have special meaning in the language. They cannot be used as identifiers. The language defines the following keywords:

Strict Keywords
asenummatchtrue
breakfalsemodtype
constfnmutuse
continueforpubwhere
ifrefwhileelse
returnletstruct

Note: Some keywords might be reserved for future use or macro rules.

Identifiers

An identifier is a name used to identify a variable, function, class, module, or other user-defined item.

Identifiers must follow these rules:

  1. They must begin with a letter (a-z, A-Z) or an underscore _.
  2. Subsequent characters may be letters, digits (0-9), or underscores _.
  3. They cannot match one of the reserved keywords.
StatusExampleReason
Validmy_var, _private, Count1Follows all rules.
Invalid1stPlaceCannot start with a digit.
Invalidmy-varHyphens are not allowed (parsed as subtraction).
Invalidifif is a reserved keyword.

Operators

Operators are special symbols or combinations of symbols used to perform operations on values or variables.

CategoryOperatorsDescription
Arithmetic+, -, *, /, %, ++, --Standard mathematical operations, increment, and decrement.
Bitwise&, |, ^, <<, >>Operations on the bit level.
Logical&&, ||, !Boolean logic (AND, OR, NOT).
Comparison==, !=, <, >, <=, >=Relational equality and ordering.
Assignment=, +=, -=, *=, /=, etc.Assigns values, optionally compound.
Structural., =>Member access, matching.

Delimiters

Delimiters are punctuation characters used to group tokens, separate lists, or define structure.

DelimiterNamePrimary Usage
( )ParenthesesFunction calls, grouping expressions, tuples.
{ }BracesBlock expressions, struct definitions, matching bodies.
[ ]BracketsArray indexing, slices, array literals.
,CommaSeparating arguments, tuple elements, and list items.
;SemicolonTerminating statements.
:ColonType annotations, struct field initialization, return types.
::Double ColonPath separation (namespaces, modules).

Items

Modules

Constant

External block

Use declarations

Functions

Structs

Enumerations

Unions

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

Statements and expressions

Eter is an expression-oriented language, meaning that most semantic constructs within the language evaluate to a value. To understand the syntactic and semantic structure of a program, it is fundamental to distinguish between statements and expressions:

  • Expressions are combinations of variables, literals, operators, and function calls that are evaluated by the compiler to compute and return a resulting value. Every expression has a well-defined type. Because they yield values, expressions can be arbitrarily nested and composed to build more complex logic.
  • Statements are instructions that dictate the control flow and state mutations of a program. They do not evaluate to a usable value (conceptually, they evaluate to the unit type unit). Their primary purpose is to execute side effects, such as introducing new bindings into a scope, modifying existing memory locations, or defining items.

In short: expressions compute values, while statements perform actions.

Expressions and statements are intrinsically linked: you can convert an expression into an expression statement by appending a semicolon ;. This forces the compiler to evaluate the expression solely for its side effects (e.g., executing a function call) while explicitly discarding any resulting value.

Statements

Statements are executed in sequence within a block. Eter supports several types of statements, including item declarations, assignments, and expression statements.

Statement terminator

Statements are generally terminated by a semicolon ;. The semicolon indicates the end of a statement and separates it from the next one.

Note

Some statements, such as those ending with a block expression (e.g., if, while, match), do not require a trailing semicolon unless they are part of a larger expression or assignment.

Assignment statements

An assignment statement evaluates an expression and binds its value to a memory location represented by a place expression (such as a variable, a struct field, or an array index).

The syntax for an assignment statement uses the = operator or a compound assignment operator (e.g., +=, -=, *=).

TypeExampleDescription
Basic Assignmentx = 5;Assigns the value 5 to the variable x.
Compound Assignmenty += 2;Adds 2 to y and assigns the result back to y.
Field Assignmentpoint.x = 10;Assigns 10 to the field x of point.

Assignment statements perform an action and do not evaluate to a value, meaning they cannot be chained like a = b = c.

Item declaration statements (let)

Item declaration statements introduce new items into the current scope. The most common item declaration within a block is the let statement, which binds a new local variable by always specifying its name and type. Variables are immutable by default unless marked with the mut keyword (more in the Memory Model Chapter).

DeclarationDescriptionExample
Variable BindingBinds a value to a new local variable, with a required type annotation.let name: str = "Alice";
Mutable BindingBinds a value to a mutable local variable.let mut count: i32 = 0;

Nested Items and Scoping

Items (such as nested let variables) can be declared inside any block scope. This allows you to restrict the visibility of an item strictly to the block it was declared in, keeping the outer namespace clean.

Nested items can be declared inside regular function blocks and unnamed (anonymous) scopes { ... } blocks. They are particularly useful inside functional blocks like if or if-else expressions to encapsulate temporary logic.

fn main() {
    let outer_val: i32 = 10;
    let condition: bool = true;

    // 1. Functional Block Scope (if-else)
    // Useful for isolating variables or helper logic
    // that is only needed for a specific branch.
    if condition {
        let inner_val: i32 = 20;
        let local_mul: i32 = 5;
        let result: i32 = inner_val * local_mul;
        do_something(result);
    } else {
        let fallback_val: i32 = 0;
        do_something(fallback_val);
    }
    // Error! `inner_val`, `local_mul`, and `fallback_val`
    // are out of scope and cannot be accessed here.
}

Expression statements

An expression statement is an expression that is evaluated for its side effects, followed by a semicolon. The value produced by the expression is discarded.

Common examples include function calls.

TypeExampleDescription
Function Calldo_something("Hello");Executes the function and discards the return value.

When a block-based expression (such as if, match, or a simple { ... } block) is used as an expression statement, the trailing semicolon is optional.


Expressions

An expression evaluates to a value and can be used in most places where a value is expected. Expressions can be nested and combined.

Literal expressions

A literal expression consists of a literal token and evaluates to the value represented by that token.

#![allow(unused)]
fn main() {
let age: i32 = 42;         // Integer literal
let name: str = "Alice";   // String literal
let is_valid: bool = true; // Boolean literal
let letter: char = 'A';    // Character literal
}

Path expressions (::)

A path expression refers to an item, variable, or constant in the current scope or another module using the path separator ::. You can also use paths to explicitly refer to the current module (self) or the parent module (super).

#![allow(unused)]
fn main() {
let max_val: u32 = u32::MAX; // Fully qualified path to a constant
let math_pi: f64 = math::PI;      // Path to a module item

// Accessing an item within the current actual scope
let current_item: i32 = self::helper_function();
}

Block expressions

A block expression is a sequence of statements enclosed in braces {}. The value of a block expression is the value of its final expression (the one without a trailing semicolon). If the block ends with a statement (with a semicolon), it evaluates to unit.

#![allow(unused)]
fn main() {
let y: i32 = {
    let x: i32 = 5;
    ret x + 1; 
}; // y is now 6
}

Operator expressions

Operator expressions apply unary or binary operators to operands.

#![allow(unused)]
fn main() {
let sum: i32 = 10 + 20;         // Binary arithmetic operator
let is_false: bool = !true;      // Unary logical NOT operator
let flag: bool = (a == b);       // Binary comparison operator
}

Grouped expressions

Parentheses () can be used to explicitly group expressions and control the order of evaluation, overriding default operator precedence.

#![allow(unused)]
fn main() {
let result: i32 = (2 + 3) * 4;  // Evaluates to 20 instead of 14
}

Access expressions

Access expressions allow you to retrieve specific elements from compound types like arrays, tuples, and structs. Depending on the underlying type, the syntax to access an element varies.

Array and index expressions

Array expressions create fixed-size collections of elements. Index expressions retrieve elements from an array or slice using brackets []. Indexing is always zero-based.

#![allow(unused)]
fn main() {
let a: [i32; 3] = [1, 2, 3];        // Array expression (list of elements)
let zeros: [i32; 5] = [0; 5];       // Array expression (repeated value: [0, 0, 0, 0, 0])
let first: i32 = a[0];              // Index expression (accessing the first element)
}

Tensor and index expressions

Tensor expressions create fixed-size, multi-dimensional collections of homogeneous elements (nD tensors). Tensor literal expressions use nested arrays.

#![allow(unused)]
fn main() {
let t: [i32; 2, 2] = [[1, 2], [3, 4]]; // Tensor expression (2x2 matrix)
let element: i32 = t[0][1];            // Index expression (accessing the element at row 0, column 1, which is 2)
}

Tuple and index expressions

Tuple expressions create ordered, fixed-size, heterogeneous collections. Tuple elements are accessed using dot . notation followed by a literal integer index.

#![allow(unused)]
fn main() {
let point: (i32, i32, str) = (10, 20, "label");  // Tuple expression
let x: i32 = point.0;                             // Tuple index expression (gets 10)
let desc: str = point.2;                         // Tuple index expression (gets "label")
}

Struct expressions and Field access

Struct expressions create instances of user-defined struct types. They specify the name of the struct and provide values for its fields. Field access expressions retrieve the value of a specific named field from a struct or union using the dot . operator.

#![allow(unused)]
fn main() {
// Struct instantiation expression
let p: Point = Point { x: 10, y: 20 }; 

// Field access expression
let my_x: i32 = p.x;                 // Accesses the 'x' field of the struct 'p'
}

Call expressions

Call expressions invoke functions or closures. They consist of an expression that evaluates to a callable entity, followed by a parenthesized list of arguments.

#![allow(unused)]
fn main() {
let result: i32 = add(5, 3);         // Function call
}

If expressions

An if expression evaluates a boolean condition and executes the corresponding block. If an else branch is provided, the if expression can evaluate to a value, provided both branches return the same type.

#![allow(unused)]
fn main() {
let condition: bool = true;

// if used as an expression to assign a value
let result: str = if condition {
    "Success"
} else {
    "Failure"
};

// if used for side effects (evaluates to `unit`)
if result == "Success" {
    do_something("Everything is fine");
}
}

Loop expressions

Loop expressions are used to execute a block of code multiple times. Because they are expressions, they can optionally evaluate to a value (e.g., by using the break keyword).

Eter supports both for and while loops.

#### While loops

The simple while loop continues to execute as long as its condition is true. Consider the following example, which decrements a variable until it reaches zero:

#![allow(unused)]
fn main() {
// while loop
let mut n: i32 = 5;
while n > 0 {
    n -= 1;
}
}

Infinite loops can be created using while true or the loop keyword, which will continue indefinitely until explicitly broken out of. For example:

#![allow(unused)]
fn main() {
// infinite loop using `while`
while true {
    do_something();
}
}

Since they are expressions, while loops can also be used to compute a value by using the break statement to exit the loop and return a value. For example:

#![allow(unused)]
fn main() {
// loop expression that evaluates to a value using `break`
let mut counter: i32 = 0;
let final_value: i32 = while true {
    counter += 1;
    if counter == 10 {
        break counter * 2; // The value of the loop expression will be 20 when it breaks
    }
};
}

For loops

Warning

Future development may introduce loop constructs, such as for in loops for iterating over collections. For instance, a for in loop might look like this:

#![allow(unused)]
fn main() {
let numbers: [i32; 5] = [1, 2, 3, 4, 5];
for num in numbers {
    do_something(num);
}
}

Match expressions

A match expression provides pattern matching. It compares a value against a series of patterns and executes the block corresponding to the first matching pattern. Like if expressions, match blocks can evaluate to a value if all branches resolve to the same type.

Patterns can include literals, variables, and the catch-all wildcard _.

Crucially, because each branch in a match must evaluate as an expression to return a value, you generally do not use a semicolon to terminate the branch’s expression. Instead, you use a comma , to separate and define the “next” pattern in the sequence.

#![allow(unused)]
fn main() {
let status_code: i32 = 404;

let message: str = match status_code {
    200 => "OK", // Notice the comma instead of a semicolon
    404 => "Not Found",
    500 => "Internal Server Error",
    // The underscore `_` acts as a wildcard, catching any unhandled values
    _ => "Unknown Error",
};
}

You can also use patterns to destructure types or bind variables:

#![allow(unused)]
fn main() {
let coords: (i32, i32) = (0, 10);

match coords {
    (0, 0) => do_something("Origin"),
    (x, 0) => do_something("On the X axis"),
    (0, y) => do_something("On the Y axis"),
    (x, y) => do_something("Somewhere else"),
}
}

Return expressions

A ret expression immediately terminates the current function or closure and evaluates to a value that is passed back to the caller. A ret expression without a value implies returning unit.

#![allow(unused)]
fn main() {
fn get_positive(val: i32) -> i32 {
    if val < 0 {
        ret 0; // Early return expression
    }
    
    ret val;
}
}

The implicit return in Eter is allowed only when there is a single expression within a scope (e.g., a function body or a block). If there are multiple statements, an explicit ret is required to indicate the return value. For instance, in the following function, the implicit return is not allowed because there are multiple statements:

#![allow(unused)]
fn main() {
fn compute_value(x: i32) -> i32 {
    let intermediate: i32 = x * 2; // Statement (requires a semicolon)
    ret intermediate + 1;           // Explicit return expression
}
}

While in this function, the implicit return is allowed because there is only a single expression:

#![allow(unused)]
fn main() {
fn compute_value(x: i32) -> i32 {
    x * 2 + 1 // Single expression (no semicolon, implicit return)
}
}

Note that, this extends to block expressions as well. If a block contains multiple statements, an explicit ret is required to return a value from that block. However, if the block consists of a single expression, it can implicitly return its value without needing ret.

#![allow(unused)]
fn main() {
let result: i32 = {
    let intermediate: i32 = 5; // Statement (requires a semicolon)
    ret intermediate + 10;     // Explicit return expression
};
let simple_result: i32 = {
    5 + 10 // Single expression (no semicolon, implicit return)
};
}

This feature is vital for writing concise and readable code, especially in cases where match and if expressions are used to compute values based on conditions. For example, in a match expression, with out the implicit return, you would need to write:

#![allow(unused)]
fn main() {
let status_code: i32 = 200;
let message: str = match status_code {
    200 => { ret "OK"; }, // Explicit return with `ret`
    404 => { ret "Not Found"; },
    500 => { ret "Internal Server Error"; },
    _ => { ret "Unknown Error"; },
};
}

Underscore expressions

The underscore _ can be used as an expression pattern to explicitly discard a value. This is useful when calling a function for its side effects, but explicitly acknowledging that you are choosing not to use its return value. It is also heavily used in match blocks as a wildcard (as shown above).

#![allow(unused)]
fn main() {
// Discard the result of a function that returns a value
let _: i32 = compute_heavy_task();
}

Qualifiers

The language uses a system of annotqualifiers) to explicitly define the memory hierarchy and movement of data across different hardware components. This provides a complete mental model for data management, from CPU RAM to GPU SRAM.

@host

  • Hardware: CPU RAM
  • Typical Usage: Dataset loading and heavy preprocessing tasks (Pandas-style). This is standard, pageable system memory.

@pinned

  • Hardware: CPU RAM (Page-locked / Pinned)
  • Typical Usage: Transit buffers used to stream data to GPUs at high bandwidth (e.g., 32GB/s+). Since it cannot be paged out to disk, it allows for faster DMA transfers to the device.

@global

  • Hardware: GPU VRAM
  • Typical Usage: Storage of model weights, biases, and gradients on a single GPU board. This is the main high-capacity memory on the device.

@sharded

  • Hardware: Cluster VRAM (Multi-GPU)
  • Typical Usage: Distribution of giant models (e.g., Llama 405B) across 8+ GPUs. It abstracts the partitioned memory across a cluster of devices.

@shared

  • Hardware: GPU SRAM (Shared Memory)
  • Typical Usage: Ultra high-speed computation inside a kernel (e.g., Tiling). This is the small, fast, on-chip memory shared by threads within the same block.

Memory model

Mutable Value Semantics (MVS)

Immutability

Mutability

Ownership

Use of Pointers (unsafe scopes)

  • let ptr<i32>
  • let mut ptr<i32>
  • let ptr<mut i32>
  • let mut ptr<mut i32>

Namespaces

Type namespace

Value namespace

Scopes

Unsafety

Unsafe scopes

unsafe {…}

Unsafe functions

unsafe fn foo() {…}