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

Essentials

After this page you can write a complete Semaprax program: functions, values, if, while, and match. Save each example as a .spx file and run it with semaprax run file.spx.

Write a program

module app.basics;

@id("basics.double")
fn double(value: i64) -> i64
{
    value * 2
}

@id("app.main")
fn main() -> i64
{
    double(21)
}

run prints 42, the value main returns. The rules:

  • A file starts with one module dotted.name; line.
  • Give every declaration an @id("dotted.name"). It is the declaration’s permanent identity. Without it, check warns SPX-S103, and renaming the function changes its identity.
  • The entry point is exactly fn main() -> i64.
  • A function body is statements followed by one final expression. That expression is the result. There is no return.
  • To use a function from another file, a Project imports it by @id with use function @id("…") from module as name;. See Modules and imports.
  • Run semaprax fmt file.spx to apply the one canonical layout. It keeps // comments.

Pick a type

TypeExampleUse it for
i6442, -1Integers. A plain integer literal is i64.
i3242i3232-bit integers.
u8255u8One byte.
usize3usizeLengths and indexes.
f64, f321.5, 1.5f32Floating point.
booltrue, falseConditions.
char'a', '\n', '\u{2603}'One Unicode scalar.
string"hello"Owned UTF-8 text. == compares contents.

These eight scalar types (everything except string) are Copy: using a value does not consume it. Text and bytes follow ownership rules, see Ownership.

Operators never mix types. If n is a usize, write n < 5usize, not n < 5 (SPX-T208). Write 5i32 when an i32 is expected, because 5 is an i64 (SPX-T232). Integer arithmetic is checked: overflow stops the program with a status such as addition overflow, never wraps.

module app.scalars;

@id("app.main")
fn main() -> i64
{
    let small = 1i32 + 2i32;
    let ratio = 1.5 * 2.0;
    let letter = 'a';
    if small == 3i32 && ratio > 2.5 && letter == 'a' && !(1 > 2) { 3 } else { 0 }
}

Use &&, ||, and ! for booleans. && and || run their right side only when needed, always left to right.

Bind a value

let count = 3; makes an immutable binding. let mut count = 3; lets you assign again with count = count + 1;. Parameters are immutable. There is no +=, and a second let with the same name is an error (SPX-T209).

To ignore a result, bind it to _: let _ = work(1);. A bare work(1); is a syntax error.

Choose a value with if

module app.branch;

@id("app.main")
fn main() -> i64
{
    let score = 42;
    let accepted = if score >= 40 { score } else { 0 };
    if accepted > 100 { 1 } else { if accepted > 10 { accepted } else { 2 } }
}

if is an expression and always has an else. Both branches have the same type. For a third case, nest an if inside the else block. There is no else if.

Repeat with while

module app.counting;

@id("app.main")
fn main() -> i64
{
    let mut next = 1;
    let mut total = 0;
    while next <= 3 {
        total = total + next;
        next = next + 1;
        next <= 3
    }
    total
}

The condition after while is checked before every pass. The body must end with an expression, and while throws its value away. Ending a body with an assignment is SPX-P203. There is no break or continue: put the exit test in the condition. See Loops for for and the loop limits.

Pick a case with match

module app.classification;

@id("app.main")
fn main() -> i64
{
    let value = -2;
    match value { 0 => 0, -1 | -2 => -9, n if n < 0 => -1, _ => 1, }
}

The first matching arm wins. | joins alternatives, if adds a guard, and _ matches anything. A match on numbers or chars needs a last arm without a guard (SPX-T257). Every arm ends with a comma, including the last. Matching variants is in Matching.

Mistakes to skip

You writeErrorWrite this
return x;SPX-P106Put x last in the block.
else if c { … }SPX-P106else { if c { … } else { … } }
i += 1;SPX-P201i = i + 1;
f(x); aloneSPX-P106let _ = f(x);
for i in 0..nSPX-P106while with a counter, or for item in vector
break, continueSPX-P106Test in the while condition.
x as i64SPX-P106No casts. Keep one type and suffix literals.
c ? a : bSPX-P106if c { a } else { b }
"a" + "b"SPX-T250string_concat("a", "b")
struct, enum, pub, constSPX-P104record, variant; no visibility keyword.
fn main() -> boolSPX-T104main returns i64. Use 0 for success.
tuples, (), fn f()SPX-P106Declare a record; every function returns a value.

When check fails, read the first error: its help: line is usually the fix. For a code, run semaprax help diagnostic SPX-T208.

Next: Types. Exact rules: RFC 0001.