Table of Contents

Values and statements

Most Raven code is organized around expressions and the values they produce, including many control-flow forms. Raven nevertheless retains a syntactic and semantic distinction between expressions and statements.

An expression computes a value and may appear where a value is expected. A statement controls execution, introduces a declaration, or evaluates an expression without passing its value to a surrounding expression. Some constructs, such as if and match, have both expression and statement syntax forms. Their surface spelling may be similar, but the parser represents them as different syntax nodes according to their context.

Calling Raven expression-oriented means that value-producing composition is available in more places than in a conventionally statement-oriented language. It does not mean that every construct is an expression or that the syntax tree erases the expression/statement boundary.

Raven has no void type. The absence of a meaningful value is represented by the unit type, which has exactly one value written (). The type itself may be spelled unit or (). Functions without an explicit return type implicitly return unit. In .NET, unit corresponds to void (see implementation notes). The unit type participates in generics and tuples like any other type.

Null and absence

Raven distinguishes nullable values from unit:

  • T? is the canonical way to represent nullable values.
  • unit (()) represents no meaningful result (void-like), not nullability.

Carrier types such as Option<T> and Result<T, E> are described in the carrier sections of this specification rather than as part of nullability.

Statements

Raven is primarily expression-oriented: most constructs yield values and can appear wherever an expression is expected. For details on statement forms, terminators, and control-flow constructs, see Control flow. Structured exception handling is covered in Error handling.

Variable bindings

let introduces an immutable lexical binding, var introduces a mutable one, and const produces an immutable binding whose value is baked in at compile time. A binding may declare its type explicitly or rely on the compiler to infer it from the initializer expression.

A lexical binding is a declaration statement, not an expression. Its initializer is an expression whose value is assigned to the newly introduced name. For example, in let x = 2 + a, 2 + a is an expression, but the whole construct is a statement. Raven has no expression form of that construct that introduces x into the surrounding lexical scope:

let x = 2 + a

let answer = 42         // inferred int

var name = "Alice"    // inferred string, mutable

const greeting = "Hi"  // inferred string constant

let count: long = 0     // explicit type

Standard Raven style prefers let for immutable lexical bindings. val remains accepted for source compatibility and is the semantic display spelling for a read-only binding.

If the type annotation is omitted, an initializer is required so the compiler can determine the variable's type. Const bindings always require an initializer, even when annotated, and the expression must be a .NET compile-time constant (numeric and character literals, true/false, strings, or null).

Value-producing forms such as if and match can be expressions. They also have statement forms when used for control flow without passing a value to a surrounding expression. while, for, and loop are statements in the current language.

Later declarations in the same scope may shadow earlier bindings. Each declaration introduces a new symbol; code that follows binds to the most recent declaration. Shadowing is permitted for both let and var bindings, but it produces the warning diagnostic RAV0168 to help catch unintentional redeclarations. Parameters of the enclosing function count as previous declarations for this purpose, so a local that reuses a parameter name both shadows it and triggers the same warning.

let answer = 41
let answer = answer + 1 // RAV0168 (warning)

File-scope code

File-scope code is supported—no Main function is required.

import System.*
alias print = System.Console.WriteLine

sayHello()

func sayHello() {
    print("Hello, World!")
}