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

The Ore Programming Language

Ore is a statically-typed, compiled programming language with indentation-based syntax and algebraic effects.

Design Principles

  • Immutable by default. Bindings are immutable. Rebinding with = creates a new shadow, not a mutation.
  • Public by default. Items are public unless marked pvt.
  • Effects are explicit. Side effects are tracked in function signatures with !(IO + State).
  • No null. Use Option<T> for optional values and Result<T, E> for fallible operations.
  • Pattern-driven. Destructuring and pattern matching are deeply integrated — in let, match, when, and multi-clause functions.
  • Functional loops. loop/recur with accumulators instead of mutable loop variables.
  • Algebraic effects for resource management. handler/with replace both exceptions and RAII/Drop.

Syntax Overview

Ore uses indentation to delimit blocks. A colon : at the end of a line introduces an indented block:

fn greet(name: String):
  let msg = "Hello, " + name
  print(msg)

Braces { } are used for struct/enum definitions and struct literals, not for blocks.

A Quick Example

enum Shape {
  Circle(f64),
  Rectangle(f64, f64),
}

fn area(s: Shape) -> f64:
  match s:
    Circle(r): 3.14159 * r ** 2
    Rectangle(w, h): w * h

fn main() !(IO):
  let shapes = [Circle(5.0), Rectangle(3.0, 4.0)]
  shapes.for_each(|s| print(area(s).show()))

Literals & Primitive Types

Integers

Integer literals default to i64. Underscores can be used as visual separators.

42
0
9999999
1_000_000

Alternate bases:

0xff          // hexadecimal (case-insensitive: 0XAB, 0xDeAdBeEf)
0b1010        // binary (0B1100)
0o77          // octal (0O55)

Integer Types

TypeSizeRange
i88-bitsigned
i1616-bitsigned
i3232-bitsigned
i6464-bitsigned
isizepointersigned
u88-bitunsigned
u1616-bitunsigned
u3232-bitunsigned
u6464-bitunsigned
usizepointerunsigned

Floats

Float literals default to f64. Underscores allowed.

3.14159
0.5
100.0
1_000.5

Scientific notation:

1e10          // 10000000000.0
1.5e10        // 15000000000.0
2.5e-3        // 0.0025
3.0e+7        // 30000000.0

Float Types

TypeSize
f3232-bit
f6464-bit

Strings

Double-quoted. The type is String.

"hello"
""                    // empty
"hello world"         // spaces

Escape sequences:

EscapeMeaning
\nnewline
\ttab
\rcarriage return
\"double quote
\\backslash
\0null byte
\'single quote

Triple-quoted strings for raw content:

"""triple quoted"""

Booleans

true
false

The type is bool.

Bytes

Single-quoted character literals produce a u8 value:

'a'           // 97
'z'
'0'
' '           // space

Escape sequences work in byte literals too:

'\n'          // newline byte
'\t'          // tab byte
'\\'          // backslash byte
'\''          // single quote byte
'\0'          // null byte

Variables & Bindings

Let Bindings

Variables are introduced with let. They are immutable by default.

let x = 5
let name = "Alice"

Optional type annotation:

let x: i64 = 5

Bindings can use any expression:

let x = 5 + 3
let y = x * 2
let result = compute(42)
let s = "hello".to_upper()

Destructuring

Tuples:

let (a, b) = get_pair()
let (a, (b, c)) = get_nested()
let (_, b) = get_pair()          // wildcard discards first element

Structs:

let Point { x, y } = get_point()
let Point { x: px, y: py } = get_point()   // rename fields
let Config { name, .. } = get_config()      // ignore remaining fields

Rebinding

The = operator rebinds a name to a new value. This is shadowing, not mutation:

fn count() -> i64:
  let x = 0
  x = x + 1          // x is now 1 (new binding, not mutation)
  x = x + 2
  x                   // 3

Compound assignment operators desugar to rebinding (see Operators):

fn count() -> i64:
  let x = 0
  x += 1
  x += 2
  x                   // 3

Field rebinding:

let p = Point::new(1, 2)
p.x = 10             // creates new binding with updated field

Constants

Top-level compile-time values with required type annotations:

const MAX_SIZE: i64 = 1024
const PI: f64 = 3.14159
const GREETING: String = "hello"
const YES: bool = true
const BYTE_A: u8 = 'a'

Constants support all literal forms:

const HEX: i64 = 0xFF
const BIN: i64 = 0b1010
const OCT: i64 = 0o77
const SCI: f64 = 1.5e10
const TRIPLE: String = """triple quoted"""

Visibility and attributes:

pvt const SECRET: i64 = 42

#[deprecated]
const OLD_VALUE: i64 = 0

Functions

Basic Functions

Functions are declared with fn. The body follows a colon and is indented:

fn add(x: i64, y: i64) -> i64:
  x + y

No return type means the function returns unit:

fn greet(name: String):
  print(name)

Short functions can be written inline:

fn double(x: i64) -> i64: x * 2
fn apply(x: i64) -> i64: compute(x)

Generics

Type parameters go in angle brackets after the function name:

fn identity<T>(x: T) -> T:
  x

With trait bounds:

fn print_it<T: Display>(x: T):
  print(x)

With where clauses:

fn convert<T, U>(x: T) -> U where T: Into<U>:
  x.into()

Effects

Functions that perform side effects declare them with !(...) after the return type:

fn read_file(path: String) -> String !(IO):
  // ...

fn complex(x: i64) -> String !(IO + State):
  // ...

See Effects & Handlers for the full effect system.

Multi-Clause Functions

Functions can pattern-match on their arguments using multiple clauses. The body contains (patterns): body clauses, similar to match:

fn fib(n: i64) -> i64:
  (0): 0
  (1): 1
  (n): fib(n - 1) + fib(n - 2)

Each clause lists patterns for the arguments in parentheses.

Function Type Parameters

Functions can accept other functions as parameters using fn(Args) -> Ret type syntax:

fn apply(f: fn(i64) -> i64, x: i64) -> i64:
  f(x)

Visibility and Attributes

Functions are public by default. Use pvt for private:

pvt fn helper() -> i64:
  42

Attributes precede the function:

#[inline]
fn fast(x: i64) -> i64:
  x * 2

Everything Combined

#[some_attr]
pvt fn kitchen_sink<T, U: Default>(x: T, y: U) -> Result<T, U> !(IO + State) where T: Display + Clone:
  // ...

Lambdas

Anonymous functions use pipe syntax:

let f = |x| x + 1
let add = |x, y| x + y
let constant = || 42

Lambdas are commonly used with higher-order functions:

items.map(|x| x * 2)
items.filter(|x| x > 0)
items.fold(0, |acc, x| acc + x)

Calling Functions

Standard call syntax:

add(5, 3)
f(g(x))
f(x + y, y * 2)

Method calls on values:

x.len()
x.concat(y)
x.trim().len()      // chaining

Path-qualified calls:

Foo::new()
std::io::File::open("test.txt")

Operators

Arithmetic

OperatorMeaningExample
+additionx + y
-subtractionx - y
*multiplicationx * y
/divisionx / y
%modulox % y
**exponentiationx ** y

Comparison

OperatorMeaningExample
==equalx == y
!=not equalx != y
<less thanx < y
<=less than or equalx <= y
>greater thanx > y
>=greater than or equalx >= y

Logical

OperatorMeaningExample
andlogical AND (short-circuit)x and y
orlogical OR (short-circuit)x or y
notlogical NOTnot x

and and or are keywords, not symbols. They short-circuit: the right operand is only evaluated if needed.

Bitwise

OperatorMeaningExample
&bitwise ANDx & y
^bitwise XORx ^ y
~bitwise NOT~x
<<left shiftx << y
>>right shiftx >> y

Unary

OperatorMeaningExample
-negation-x
notlogical NOTnot x
~bitwise NOT~x

Precedence

From highest to lowest binding power:

PrecedenceOperatorsAssociativity
25. () ?left (postfix)
21- not ~ (unary)prefix
19**right
17* / %left
15+ -left
13<< >>left
11&left
9^left
7== != < <= > >=left
5andleft
3orleft
1= += -= *= /= %=right

Parentheses override precedence:

x + y * z       // y * z first
(x + y) * z     // x + y first
-a + b ** 2     // (-a) + (b ** 2)

Compound Assignment

OperatorDesugars to
x += yx = x + y
x -= yx = x - y
x *= yx = x * y
x /= yx = x / y
x %= yx = x % y

Works on fields too:

p.x += 10        // p = Point { x: p.x + 10, ..p }
lexer.pos += len

Try Operator

The postfix ? operator propagates errors from Result values:

fn read_config(path: String) -> Result<Config, Error>:
  let contents = read_file(path)?     // returns Err early if read fails
  let config = parse(contents)?
  Ok(config)

? desugars to a match on the Try trait — on Err, it returns the error; on Ok, it unwraps the value.

Chaining:

fn combine(x: Result<i64, String>, y: Result<i64, String>) -> Result<i64, String>:
  Ok(x? + y?)

Types

Structs

Unit Structs

No fields:

struct Unit
pvt struct PvtUnit

Newtype / Tuple Structs

Positional fields in parentheses:

struct Newtype(i64)
struct Pair(i64, i64)

Fields can have visibility:

struct TupleVis(pvt i64, String)

Record Structs

Named fields in braces:

struct Point {
  x: i64,
  y: i64,
}

Field visibility:

struct RecordVis {
  pvt name: String,
  age: i64,
}

Generics

Type parameters with optional bounds:

struct Container<T> {
  value: T,
}

struct Bounded<T: Clone + Debug> {
  value: T,
}

struct Multi<T: Clone, U: Display> {
  first: T,
  second: U,
}

Where Clauses

Complex bounds can use where:

struct WithWhere<T, U> where T: Clone + Debug, U: Display {
  first: T,
  second: U,
}

struct BoundedTuple<T>(T) where T: Clone

Attributes

#[derive(Debug, Clone)]
struct Annotated {
  field: i64,
}

#[derive(Debug)]
#[repr(C)]
struct CRepr {
  x: i32,
  y: i32,
}

Multiple attributes can be stacked.

Everything Combined

#[derive(Debug, Clone, PartialEq)]
pvt struct FullBellsAndWhistles<T, U: Default> where T: Clone + Debug {
  pvt id: i64,
  name: String,
  data: T,
  extra: U,
}

Recursive Structs

Use Box for indirection:

struct List<T> {
  head: T,
  tail: Option<Box<List<T>>>,
}

Struct Literals

Creating struct values:

Point { x: 1, y: 2 }

Shorthand when variable names match fields:

let x = 1
let y = 2
Point { x, y }         // same as Point { x: x, y: y }

Spread to copy remaining fields:

let p = Point { x: 1, y: 2 }
Point { x: 10, ..p }   // y comes from p

Path-qualified:

std::geo::Point { x: 1, y: 2 }

Nested:

Line { start: Point { x: 0, y: 0 }, end: Point { x: 1, y: 1 } }

Enums

Unit Variants

C-style enumerations:

enum Color { Red, Green, Blue }
pvt enum Internal { A, B }

Tuple Variants

Variants carrying unnamed data:

enum Option<T> {
  Some(T),
  None,
}

enum Result<T, E> {
  Ok(T),
  Err(E),
}

Record Variants

Variants with named fields:

enum Shape {
  Circle { radius: f64 },
  Rectangle { width: f64, height: f64 },
}

Mixed Variants

An enum can mix unit, tuple, and record variants:

enum Message {
  Quit,
  Move { x: i64, y: i64 },
  Write(String),
  ChangeColor(i64, i64, i64),
}

Generics, Bounds, and Where Clauses

enum Wrapper<T: Clone> {
  Item(T),
  Empty,
}

enum Constrained<T, U> where T: Clone, U: Debug {
  Both(T, U),
  First(T),
}

Recursive Enums

enum Tree<T> {
  Leaf(T),
  Node(Box<Tree<T>>, Box<Tree<T>>),
}

GADTs

Variants can specialize their return type with ->:

enum Expr<T> {
  LitInt(i64) -> Expr<i64>,
  LitBool(bool) -> Expr<bool>,
  Add(Expr<i64>, Expr<i64>) -> Expr<i64>,
  If(Expr<bool>, Expr<T>, Expr<T>) -> Expr<T>,
  Eq(Expr<i64>, Expr<i64>) -> Expr<bool>,
}

This enables type-safe expression trees where the type parameter tracks the result type of each node.

Type Aliases

type Byte = i8
type Result<T> = std::result::Result<T, MyError>
type Pair<A, B> = Tuple2<A, B>
type CloneVec<T: Clone> = Vec<T>
type Mapped<T, U> = Map<T, U> where T: Clone, U: From<T>
pvt type Internal = InternalImpl

Tuples

Ordered, fixed-size groups of values:

(1, 2)
(42, "hello", true)
(1, (2, 3))          // nested

Arrays

Fixed-size, homogeneous collections:

[1, 2, 3]
[]                    // empty
["hello", "world"]
[[1, 2], [3, 4]]     // nested

Array types are written [T; n]:

fn make_array() -> [i64; 3]:
  [1, 2, 3]

Traits & Impls

Traits

Marker Traits

Traits with no methods:

trait Marker

Methods

trait Greet:
  fn hello(self) -> String
  fn goodbye(self) -> String

Default Implementations

Methods can have a default body:

trait WithDefaults:
  fn required(self) -> i64
  fn optional(self) -> String:
    "default"

Associated Types

Types declared inside a trait, defined by each impl:

trait Iterator:
  type Item
  fn next(self) -> Option<Self::Item>

Associated types can have bounds:

trait Collection:
  type Item: Clone + Debug
  fn first(self) -> Self::Item

Associated Constants

trait HasSize:
  const SIZE: i64

Generic Traits

trait Into<T>:
  fn into(self) -> T

Supertraits

A trait can require other traits with with:

trait Sortable with Ord:
  fn sort(self) -> Self

trait Printable with Display + Debug:
  fn print(self) -> String

Where Clauses

trait Convert<T, U> where T: Clone, U: From<T>:
  fn convert(self, value: T) -> U

Generic Methods

Methods can have their own type parameters:

trait Mapper:
  fn map<U>(self, f: fn(Self) -> U) -> U

Static Methods

Methods without self are static (called on the type, not an instance):

trait Factory:
  fn create() -> Self
  fn default() -> Self

Visibility and Attributes

pvt trait InternalTrait:
  fn internal(self) -> i64

#[some_attr]
trait Documented:
  fn describe(self) -> String

Everything Combined

#[some_attr]
pvt trait FullTrait<T, U: Default> with Clone + Debug where T: Display:
  type Output: Clone
  const MAX: i64
  fn required(self, input: T) -> Self::Output
  fn optional(self) -> U:
    U::default()
  fn static_method() -> Self
  fn generic<V>(self, v: V) -> V where V: Into<T>

Impls

Inherent Impls

Methods on a type (no trait):

impl Point:
  fn new(x: i64, y: i64) -> Point:
    // ...

  fn distance(self) -> f64:
    // ...

Trait Impls

Implementing a trait for a type:

impl Display for Point:
  fn display(self) -> String:
    // ...

Generic Impls

impl<T> Container<T>:
  fn unwrap(self) -> T:
    // ...

impl<T: Clone> Clone for Container<T>:
  fn clone(self) -> Container<T>:
    // ...

Blanket Impls

Implement a trait for all types matching a bound:

impl<T> Show for T where T: Display:
  fn show(self) -> String:
    // ...

Multi-Clause Methods

Pattern matching on self in impl methods:

impl<T, E> Result<T, E>:
  fn is_ok(self) -> bool:
    (Ok(_)): true
    (Err(_)): false

The body contains (patterns): body clauses, like a match on the arguments.

Associated Type and Const Impls

impl Iterator for Counter:
  type Item = i64
  fn next(self) -> Option<i64>:
    // ...

impl HasSize for Point:
  const SIZE: i64 = 2

Private Methods and Impls

impl Point:
  fn public_method(self) -> i64:
    // ...
  pvt fn helper(self) -> i64:
    // ...

pvt impl InternalTrait for Point:
  fn internal(self) -> i64:
    // ...

Everything Combined

#[some_attr]
pvt impl<T, U: Default> FullTrait<T, U> for FullBellsAndWhistles<T, U> where T: Display:
  type Output = T
  const MAX: i64 = 100
  fn required(self, input: T) -> T:
    // ...
  fn optional(self) -> U:
    // ...
  fn static_method() -> Self:
    // ...
  fn generic<V>(self, v: V) -> V where V: Into<T>:
    // ...

Control Flow

Ore has no if/else. Instead, it uses when for conditionals, match for pattern matching, and loop/recur for iteration.

When

Multi-Arm Conditional

when: with indented arms replaces if-else chains. Use _ for the default case:

fn classify(x: i64) -> String:
  when:
    x > 0: "positive"
    x < 0: "negative"
    _: "zero"

Arms with multiple statements:

fn process(x: i64) -> i64:
  when:
    x > 0:
      let doubled = x * 2
      doubled + 1
    _: 0

Single Guard

A standalone when acts as an early exit or guard:

fn clamp(x: i64) -> i64:
  when x > 100: return 100
  when x < 0: return 0
  x

Match

Pattern matching on a value:

fn describe(opt: Option<i64>) -> String:
  match opt:
    Some(v): v.show()
    _: "nothing"

Literal Patterns

match x:
  0: "zero"
  1: "one"
  _: "other"

Enum Patterns

match result:
  Ok(v): v
  Err(msg): -1

Or Patterns

Multiple patterns separated by |:

match x:
  1 | 2 | 3: "small"
  4 | 5 | 6: "medium"
  _: "large"

Range Patterns

Exclusive ranges with ..:

match x:
  0..10: "single digit"
  10..100: "double digit"
  _: "big"

Guards

Add conditions with when:

match opt:
  Some(x) when x > 0: "positive"
  Some(x) when x < 0: "negative"
  Some(_): "zero"
  _: "none"

Nested Patterns

match x:
  Some(Some(v)): v
  Some(_): -1
  _: -2

Struct Patterns

match p:
  Point { x: 0, y: 0 }: "origin"
  Point { x, y }: "other"

Multi-Statement Arms

match x:
  0:
    let result = compute()
    result + 1
  _: x

Loop and Recur

Ore uses functional loops. loop creates a loop with named accumulators; recur jumps back with new values.

Accumulator Loop

fn sum_to_ten() -> i64:
  loop (i = 0, sum = 0):
    when i >= 10: sum
    recur(i + 1, sum + i)

The loop binds i and sum to initial values. recur(...) restarts the loop with new values. When a non-recur expression is reached, the loop returns that value.

Single Binding

fn double_until() -> i64:
  loop (x = 1):
    when x >= 100: x
    recur(x * 2)

Complex Loops

Loops can contain any expression, including match:

fn find(items: Vector<i64>, target: i64) -> Option<i64>:
  loop (i = 0):
    when i >= items.len(): None
    match items.get(i):
      Some(v) when v == target: Some(i)
      _: recur(i + 1)

Collecting

Build up results with an accumulator:

fn squares() -> Vector<i64>:
  loop (i = 0, result = Vector::new()):
    when i >= 5: result
    recur(i + 1, result.push(i * i))

Bare Loop

Infinite loop with explicit return:

fn repl() -> i64:
  loop:
    let line = read_line()
    when line == "quit": return 0
    process(line)

While-Style Loop

A condition after loop is checked each iteration:

fn process_connection(conn: Connection) -> i64:
  loop conn.is_alive():
    process(conn)

Return

return exits the function early with a value:

fn early_exit(x: i64) -> i64:
  when x < 0: return -1
  x * 2

Effects & Handlers

Ore uses algebraic effects for side effects, resource management, and control flow. The system has three constructs:

  • effect — declares an effect and its operations
  • handler — defines an implementation of an effect
  • with — installs a handler for a scope

Declaring Effects

An effect declares a set of operations that code can perform:

effect State<S>:
  fn get() -> S
  fn set(s: S)

Operation Kinds

There are three kinds of effect operations:

KindMeaningContinuation
fnTail-resumptive: always resumes exactly onceNo continuation captured (cheap)
ctlControl: may resume zero or many timesCaptures continuation
finalFinal: never resumesNo continuation (like exceptions)
effect Async<T>:
  fn spawn(task: fn() -> T) -> T     // always resumes
  ctl yield() -> T                    // may suspend/resume
  ctl abort(reason: String) -> Never  // may not resume

effect Error:
  final throw(msg: String) -> Never   // never resumes

fn is the cheapest — use it when the handler always returns a value and continues. ctl captures a continuation, which the handler can call to resume. final is an optimization hint: the compiler knows no continuation is needed.

Marker Effects

Effects with no operations:

effect Pure

Super Effects

An effect can extend others with with:

effect FileIO with IO:
  fn read_file(path: String) -> String
  fn write_file(path: String, content: String)

effect NetworkIO with IO + Exception:
  fn fetch(url: String) -> String

Generics, Bounds, and Where Clauses

effect Transform<T: Clone>:
  fn transform(value: T) -> T

effect Convert<T, U> where T: Clone, U: From<T>:
  fn convert(value: T) -> U

Visibility and Attributes

pvt effect InternalEffect:
  fn internal_op() -> i64

#[some_attr]
effect Documented:
  fn describe() -> String

Everything Combined

#[some_attr]
pvt effect FullEffect<T, U: Default> with IO + Exception where T: Clone:
  fn required(value: T) -> U
  ctl fail(msg: String) -> Never
  final panic(msg: String) -> Never

Declaring Effects on Functions

Functions declare which effects they perform with !(...):

fn read_file(path: String) -> String !(IO):
  // ...

fn complex(x: i64) -> String !(IO + State):
  // ...

Handlers

A handler expression defines how to handle an effect’s operations. Operation parameters are just names (types come from the effect definition):

fn simple_handler():
  handler State:
    get(): state
    set(s): state = s

Return Clause

Process the final value of the handled computation:

fn state_handler(init: i64):
  state = init
  handler State:
    get(): state
    set(s): state = s
    return(val): val

Initially and Finally

Resource management without a Drop trait:

fn file_handler(path: String):
  let fd = Os::open(path)
  handler File:
    initially: print("opening")
    finally: Os::close(fd)
    read(buf, len): Os::read(fd, buf, len)
    write(data): Os::write(fd, data)

initially runs when the handler is installed. finally runs when the handled scope exits (whether normally or via a final operation).

All Clauses

fn full_handler():
  let count = 0
  handler Logger:
    initially: print("logger started")
    finally: print("logger stopped")
    log(msg):
      count = count + 1
      print(msg)
    get_count(): count
    return(val): (val, count)

Resume

In ctl handlers, resume(value) continues the captured continuation with a value:

fn example() -> i64:
  with handler Ask:
    ask():
      resume(42)
  Ask::ask() + 1    // ask() resumes with 42, so this returns 43

Not calling resume short-circuits — the handler’s value becomes the result of the entire with block:

fn safe_div(x: i64, y: i64) -> i64:
  with handler Fail:
    fail(msg):
      -1              // no resume → with block returns -1
  when y == 0:
    Fail::fail("division by zero")
  x / y

resume can be called multiple times for effects like non-determinism:

fn example() -> List<i64>:
  with handler Choose:
    choose():
      let a = resume(true)
      let b = resume(false)
      a ++ b
  let x = when Choose::choose(): 1 else: 2
  [x]

fn operations auto-resume (no explicit resume needed). final operations never resume.

With Expressions

with installs a handler for a block of code.

Scoped Body

fn example() -> i64:
  let (state, x) = with state_handler(0):
    State::set(42)
    State::get()
  x + 1

Rest of Scope

Without a : body, the handler covers the rest of the enclosing scope:

fn example() -> i64:
  with state(0)
  State::set(10)
  State::get()

Nested Handlers

fn example() -> i64:
  with diagnostic_stderr():
    with state_handler(0):
      State::set(42)
      State::get()

Inline Handler

Define and install a handler in one expression:

fn example() -> i64:
  let x = with handler State:
    get(): 42
    set(s): state = s
  x

Single-Operation Shorthand

For handlers with a single operation, skip the handler keyword — the effect is inferred from the operation name:

fn example() -> i64:
  with ctl fail(msg):
    -1
  safe_div(10, 0)

fn example2() -> i64:
  with ctl ask():
    resume(42)
  Ask::ask() + 1

fn example3() -> i64:
  with fn get():
    42
  State::get()

This is equivalent to with handler Effect: op(params): body but more concise for single-operation cases.

Modules & Imports

Modules

File-Based Modules

Declare a module that maps to a file:

mod utils

This looks for utils.ore in the same directory.

Inline Modules

Define a module’s contents inline:

mod math:
  fn add(x: i64, y: i64) -> i64:
    x + y

Inline modules can contain any items (structs, functions, nested modules, etc.):

pvt mod helpers:
  struct Helper
  fn assist() -> i64:
    42

Visibility and Attributes

pvt mod internal

#[cfg(test)]
mod tests:
  fn test_add() -> bool:
    true

Imports

Simple Path

Import a module:

use std::io

Specific Item

use std::io::File

Glob Import

Import everything from a module:

use std::io::*

Aliases

Rename on import:

use std::io as sio
use std::io::File as F

Tree Imports

Import multiple items from the same path:

use std::io::{File, BufReader}

Nested trees:

use std::{io::{File, BufReader}, collections::HashMap}

Aliases inside trees:

use std::io::{File, Write as W}

Special Path Segments

SegmentMeaning
superParent module
ignotPackage/crate root
selfCurrent module
use super::something
use super::super::deep
use ignot::parser::ast::Item
use std::io::{self, File}

Private Imports

pvt use std::io::File

Intrinsics

Intrinsics are compiler-builtin operations prefixed with @. They bypass normal name resolution and map directly to low-level operations.

Syntax

@name(args)

Examples:

@add(x, y)
@nop()
@fma(a, b, c)

Purpose

Intrinsics exist for operations that cannot be expressed in Ore itself:

  • Arithmetic primitives@add<T>, @sub<T>, @mul<T>, etc. (used to implement operator traits)
  • Memory operations@alloc, @free, @load<T>, @store<T>, @copy
  • System calls@write, @read, @open, @close, @exit
  • Type operations@sizeof<T>, @as<To> (casting)
  • Process control@panic, @exit, @arg_count, @arg

Regular Ore code rarely uses intrinsics directly. They are primarily used in the standard library to implement traits and effects.

Keyword Reference

KeywordUsage
andLogical AND (short-circuit): x and y
constCompile-time constant: const N: i64 = 42
ctlControl effect operation (captures continuation)
effectDeclare an algebraic effect
enumDeclare an enumeration type
falseBoolean literal
finalFinal effect operation (never resumes)
fnDeclare a function, or a tail-resumptive effect operation
forTrait implementation target: impl Trait for Type
handlerDefine an effect handler
ignotPackage/crate root in paths
implImplement methods or traits for a type
letBind a value to a name
loopFunctional loop with accumulators
matchPattern matching on a value
modDeclare a module
notLogical NOT: not x
orLogical OR (short-circuit): x or y
pvtMark an item as private (public by default)
recurJump back to enclosing loop with new values
resumeResume continuation in ctl handler: resume(value)
returnReturn a value from a function early
SelfThe implementing type in traits/impls
selfThe receiver in methods, or current module in paths
structDeclare a struct type
superParent module in paths
traitDeclare a trait
trueBoolean literal
typeDeclare a type alias
useImport items from a module
whenConditional expression or match guard
whereIntroduce type bounds on generic parameters
withInstall an effect handler, or declare supertraits/supereffects

Reserved Symbols

SymbolUsage
@Intrinsic prefix: @add(x, y)
?Try/error propagation: expr?
#[...]Attribute annotation
!(...)Effect declaration on functions: !(IO + State)
|...|Lambda parameters: |x| x + 1
::Path separator: Foo::bar
..Spread in struct literals, range in patterns
->Return type separator
_Wildcard pattern