Table of Contents

Values, expressions, and statements

Raven is expression-oriented: most computations produce values, and constructs such as if and match can be used directly where a value is expected.

let label = if score >= 50 then "pass" else "fail"

Raven still distinguishes expressions from statements. An expression computes a value. A statement introduces a declaration, performs an action, or controls which code runs without passing a value to a surrounding expression.

let message = "ready"        // declaration statement
Console.WriteLine(message)   // expression statement

let length = message.Length  // the initializer is an expression

Some constructs, including if, match, try, and throw, have both expression and statement forms. Their spelling can be similar, but their role is determined by context. Other constructs, such as while, for, and loop, are statements.

See Expressions and type inference for value-producing forms, Statements for assignment and effectful forms, and Control flow for how context and newlines distinguish them.

The unit value

Raven has no void type. A computation with no meaningful result produces the single unit value, written ():

func log(message: string) -> unit {
    Console.WriteLine(message)
}

let completed: unit = ()

The type itself may be written unit or (). A function without an explicit return type returns unit.

unit is a real value type. It can be used in generics, tuples, and unions, and maps to System.Unit when Raven code is represented on .NET. At CLR method boundaries it corresponds to a method that returns void; see .NET implementation notes.

The type is also a source-level promise that no meaningful value flows out of a computation. A non-unit expression in the final position of a unit function is diagnosed as an unused result. Discard it intentionally with _ = expression:

func save() {
    _ = tryWriteFile()
}

Null and absence

unit does not represent a missing value. Raven distinguishes these concepts:

  • unit or () means a computation has no meaningful result.
  • T? means a value of type T may be null.
  • Option<T> represents explicit domain-level presence or absence.
  • Result<T, E> represents success or an expected error.

See the Type system for nullable types and Error propagation and carrier types for Option and Result.

Declarations and file-scope code

Local values are introduced with let, var, const, or use. Their mutability, inference, deconstruction, and cleanup behavior is described under Local declarations.

Executable statements can also appear directly in a source file, so a small program does not need an explicit Main function:

import System.Console.*

let name = "Raven"
WriteLine("Hello, $name!")

See Top-level code and entry points for file-scope restrictions and executable entry-point selection.