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

Syntax at a Glance

Compressed reference for variables, functions, operators, control flow, and builtins. No semicolons – Pluto uses newline-based statement termination. Comments with // only (no block comments).

Variables

let x = 42              // inferred as int
let x: int = 42         // explicit type annotation
let pi = 3.14           // float (f64)
let name = "alice"      // string
let flag = true         // bool
let b = (65).to_byte()  // byte (0-255, unsigned)

Variables are immutable by default – reassigning a plain let is a compile error. Declare with let mut to allow reassignment:

let mut x = 1
x = 2
x = x + 10

Primitive Types

TypeSizeDescription
inti6464-bit signed integer
floatf6464-bit floating point
booli8true or false
stringheapHeap-allocated, immutable chars
byteu8Unsigned 0-255

Numeric literals support underscores: 1_000_000, 1_000.50. Hex literals: 0xFF, 0x0A.

Type Conversions

Explicit only, as named methods. No implicit coercion and no as cast. Widening conversions are total; a conversion that can lose information returns a nullable, which the compiler narrows to a plain value where it can prove the input is in range (a literal, a guard, a mask).

(42).to_float()          // int -> float
(3.14).to_int()          // float -> int?  (none for NaN/inf/out of range; truncates toward zero)
true.to_int()            // bool -> int (true = 1, false = 0)
n != 0                   // int -> bool is just a comparison
(255).to_byte()          // int -> byte?  (none outside 0..255; a literal narrows to byte)
(300).low_byte()         // int -> byte, keeping the low 8 bits by name
b.to_int()               // byte -> int (zero-extends)
"42".to_int()            // string -> int?  (none if it does not parse)

Operators

Arithmetic (int and float):

OpMeaningCompound
+add / string concat+=
-subtract-=
*multiply*=
/divide/=
%modulo%=

Increment/decrement (int only): x++, x--

Comparison (returns bool): ==, !=, <, >, <=, >=

Logical (bool only): &&, ||, !

Bitwise (int only): & (AND), | (OR), ^ (XOR), ~ (NOT), << (shl), >> (shr). Precedence: & > ^ > |.

Functions

fn add(a: int, b: int) int {
    return a + b
}

fn greet(name: string) {
    print(f"hello {name}")
}

Void functions omit the return type. All non-void paths require an explicit return – no implicit return of the last expression. Trailing commas allowed in argument lists.

String Interpolation

Arbitrary expressions inside {} within an f-prefixed string. A plain "..." string never interpolates – f"{x}" is the literal text {x}.

print(f"hello {name}")          // hello alice
print(f"{a} + {b} = {a + b}")   // 1 + 2 = 3
print(f"use {{braces}}")        // use {braces}  (escape with doubling)

Supports int, float, bool, string, byte. Class instances cannot be interpolated.

Escape sequences: \n, \r, \t, \\, \"

Control Flow

if / else if / else:

if x > 10 {
    print("big")
} else if x > 0 {
    print("small")
} else {
    print("non-positive")
}

while:

let i = 0
while i < 10 {
    print(i)
    i++
}

for-in – arrays, ranges, and strings:

for x in [1, 2, 3] {           // array iteration
    print(x)
}

for i in 0..10 {               // exclusive range: 0..9
    print(i)
}

for i in 0..=10 {              // inclusive range: 0..10
    print(i)
}

for c in "abc" {               // string: yields one-byte strings
    print(c)
}

Range endpoints can be expressions: for i in 0..(n * 2) { ... }

break / continue: work in both while and for. break exits the innermost loop. Cannot be used inside closures.

Generators

A function whose return type is stream T is a generator. Calling it creates the stream without running the body. Each turn of a for loop over the stream runs the body up to its next yield, which hands one value to the loop.

fn evens(limit: int) stream int {
    for i in 0..limit {
        if i % 2 == 0 {
            yield i
        }
    }
}

fn main() {
    for n in evens(10) {
        print(n)            // 0, 2, 4, 6, 8
    }
}
  • The stream ends when the body finishes or runs a bare return. A generator needs no return.
  • yield can appear inside if, while, for and match arms, at any depth. It cannot appear inside a closure.
  • Generators are lazy, so an infinite one is fine. A break in the consuming loop simply stops asking for values.
  • A stream is single-pass. Iterating it a second time yields nothing, so call the generator again for a fresh stream.
  • Generators can be generic and can consume other streams:
fn take<T>(s: stream T, count: int) stream T {
    let mut i = 0
    for x in s {
        if i >= count {
            return
        }
        yield x
        i = i + 1
    }
}

Generators must be top-level functions. A method cannot be a generator.

Builtins

FunctionSignatureNotes
print(val)any printablePrints with newline
abs(x)int/floatAbsolute value
min(a, b)int/floatMinimum
max(a, b)int/floatMaximum
pow(base, exp)int/floatpow(int, negative) raises MathError
sqrt(x)floatSquare root
floor(x)floatFloor
ceil(x)floatCeiling
round(x)floatRound to nearest
sin(x), cos(x), tan(x)floatTrig (radians)
log(x)floatNatural logarithm
time_ns()-> intMonotonic nanosecond timestamp

Builtin names are reserved – user functions cannot shadow them.

Quick Comparison

ConceptGoRustPluto
Variablex := 42let mut x = 42;let mut x = 42
Type annotationvar x int = 42let x: i64 = 42;let x: int = 42
String interpfmt.Sprintf("hi %s", n)format!("hi {n}")f"hi {n}"
Range loopfor i := 0; i < 10; i++for i in 0..10for i in 0..10
No return valueimplicitimplicitomit return type
Terminatornewline/;;newline
Mutabilitymutable defaultimmutable defaultimmutable default