Expression and Call Semantics

Assignment Semantics

The language has two assignment operators:

  • = means logical value copy.
  • <- means ownership transfer that invalidates a named source binding.

Syntax Snapshot (MVP)

  • Function declarations:
  • fn name(p1: T, p2: &U, p3: V) -> ReturnType { ... }
    • Return types are required in function signatures.
    • Return annotations do not accept ownership sigils; return values are always owned.
  • Assignment:
    • let b = a (read-only view local; source stays valid)
    • let @b = a (owned local initialized from a)
    • let b <- a (read-only view local initialized by move; source is invalid afterward)
    • let @b <- a (owned local initialized by move; source is invalid afterward)
    • let b <- expr and let @b <- expr are invalid when expr is not a named place
    • place.field = expr updates a struct field through a named place path
  • Array and slice types:
    • [T; N] is a fixed-size array type
    • [T] is an owned dynamically sized array type
  • Array literals:
    • [e1, e2, ...] is an array literal (Rust-like)
    • [] is an empty array literal and needs array type context
    • f(<-x) explicit move from named binding
    • f(rvalue_expr) implicit move from rvalue
  • Expression statements:
    • expr; discards the expression result regardless of type
  • Integer arithmetic:
    • unary -x requires x: i64 and returns i64
    • binary +, -, *, /, % require i64 operands and return i64
    • precedence follows * / % above + -, and unary - binds tighter than both

Copy Assignment (=)

  • Source and destination remain valid after assignment.
  • Copy assignment is valid only for copyable values.
  • Runtime performs an eager owned copy for copyable heap-backed values.

Local Declarations

  • let x = expr creates a read-only view local.
  • let &x = expr creates a mutable reference local.
  • let x <- y creates a read-only local that owns the moved value from y.
  • let @x = expr creates an owned local.
  • Assigning to x or borrowing &x requires let @x or let &x = ...; moving <-x still requires let @x.
  • Assigning to x is valid when x was declared with let &x = ....
  • let x <- y invalidates y, but x still remains read-only after the move.

Read-only parameters and read-only locals do not have identical storage semantics:

  • A parameter x: T may read directly from caller-owned storage.
  • A local let x <- y becomes the new read-only owner after the move and does not alias a still-live source binding.

Move Assignment (<-)

  • <- is only valid when the source is a named binding/place.
  • Destination receives ownership and source becomes invalid.
  • <- on rvalues/temporaries is invalid (there is no name to invalidate).
  • Using moved-from source is a runtime or compile-time error (depending on checker stage).

Arrays and Slices

  • [T; N] is an owned value and participates in =, <-, and <-x like other owned values.
  • [T] is an owned runtime-sized array value and is represented as an owned contiguous sequence.
  • In parameter mode T, [T] means a read-only slice view.
  • In parameter mode &T, [T] means a mutable slice view.
  • Slice parameters can accept array arguments with element-compatible item types.
  • @box(expr) allocates a non-null pointer value of type *T.
  • @array(init, len) constructs heap arrays ([T]) by inferring T from init.
  • @as(T, x) performs compile-time-safe numeric casts only.
  • @intCast(T, x) performs checked integer casts and traps on overflow.
  • @intToFloat(T, x) converts integer values to floating-point values.
  • @trunc(T, x) allows narrowing integer-to-integer and float-to-float casts.

Checked cast edge cases: @intCast(i8, 120i16) succeeds, while @intCast(u8, -1i16) and @intCast(i8, 255u16) trap.

Expression-Oriented Semantics

The language is expression-oriented (Rust-style): control-flow constructs and blocks are expressions.

Unit Result

  • Unit is implicit in syntax (no required () literal in MVP).
  • A block with no tail expression yields unit.
  • while yields unit.

if Expression Rules

  • if (cond) { then } else { other } is an expression.
  • if (cond) { then } (no else) is allowed only if then yields unit.
  • With else, both branches must yield compatible result categories.

Pointer and Option Semantics

Pointers are non-null owned handles used for explicit indirection.

  • Pointer types are written as *T.
  • @box(expr) constructs a pointer value of type *T when expr: T.
  • Option[T] is the built-in optional type constructor; use Option[*T] when a boxed value may be absent.
  • *expr dereferences a pointer expression and reads the pointee as a value.
  • Field/index projection through a pointer base implicitly dereferences as needed (for example node.next.value where node.next: *Node).
  • Passing &b when b: *T borrows the pointee as &T.
  • &*b is rejected.
  • *T adds exactly one explicit heap indirection whose allocation stores T inline.
  • The immediate payload T cannot itself be another pointer or a built-in heap-handle type such as String or owned runtime-sized array values.

Pointer and Optional Construction Examples

  • let @head = Some(@box(Node { value: 1, next: None() }));
  • match (head) { Some(node) => node.value, None() => 0 };

Result Discard Rules

  • Any expression statement form expr; discards the result.
  • let _ = expr, let _ <- expr, and let @_ <- expr are ordinary bindings to _ (not special discard forms).

Iteration Semantics

The language does not expose first-class references, so iterator design avoids storing borrowed references in user-visible iterator objects.

for Forms

  • for (collection) |x| { ... }
    • Plain binder |x| is a read-only view of each element.
  • for (collection) |&x| { ... }
    • Mutable-borrow binder |&x| follows &T semantics (exclusive mutable borrow per iteration).
  • for (<-collection) |x| { ... }
    • Consuming traversal form; <-collection invalidates the named collection binding.
    • Elements are yielded to x using the normal plain-binder read-only view semantics.

collection may be [T; N] or [T].

Normalization Rule

  • for (<-collection) |item| { ... } consumes collection and invalidates its binding.
  • for (collection) |<-item| { ... } is invalid; iteration does not move elements out directly.

Single-Form Principle

  • Redundant forms are disallowed to keep one canonical way to express behavior.
  • <- is valid on the iterable expression only for consuming traversal from a named collection.

Call Semantics

Index and Slice Access

  • xs[i] reads through a read-only view.
  • xs[a..b] produces a read-only slice view.
  • &xs[i] and &xs[a..b] request mutable borrows.
  • let x = place_expr and let &x = place_expr can bind references to fields/indexed items for the binding scope.
  • Local mutable borrows are checked for overlap: disjoint struct fields may coexist, while index/slice projections on the same root are conservatively treated as overlapping.
  • Copyable indexed elements may still be copied into owned locals or other owned contexts when required.
  • Plain slice views are not copied into owned values implicitly.

Argument Forms

  • f(x)
    • Valid for T (read-only view) and @T (owned copy).
  • f(&x)
    • Valid only when parameter type is &T.
  • f(<-x)
    • Explicit move into an owned parameter (@T), invalidating named binding x.
  • f(rvalue_expr)
    • For T, bare rvalues are borrowed for the duration of the call.
    • For @T, bare rvalues are passed as owned temporaries.

Parameter Mode Rules

For a parameter declared as T:

  • Passing f(x) borrows a read-only view.
  • Passing f(&x) or f(<-x) is invalid.
  • The callee cannot assign through the parameter binding.

For a parameter declared as &T:

  • Passing f(&x) creates a mutable borrow.
  • Other argument forms are invalid.

For a parameter declared as @T:

  • Passing f(x) copies into a fresh owned value.
  • Passing f(<-x) moves ownership and invalidates x.
  • Passing f(rvalue) moves the temporary into the callee.

<- exists only to invalidate named bindings; temporaries already have no source binding to invalidate.

Copy Strategy

Copyable heap aggregates (String, arrays, records) use eager copy semantics for = and owned argument copies.

Linear values cannot have aliases other than statically-checked borrows.