Functions and Procedures

Part of: Reef Language Reference Last reviewed on version: 0.8.0


Functions vs Procedures

Functions (fn) - Return a value:

fn add(a: int, b: int): int
    return a + b
end add

Procedures (proc) - No return value:

proc greet(name: string)
    println(name)
end greet

Function Syntax

fn name(param1: type1, param2: type2): return_type
    statements
    return value
end name

Example:

fn multiply(x: int, y: int): int
    return x * y
end multiply

proc main()
    let result = multiply(6, 7)
    print_int(result)           // 42
    println("")
end main

Procedure Syntax

proc name(param1: type1, param2: type2)
    statements
end name

Example:

proc print_sum(a: int, b: int)
    let sum = a + b
    print_int(sum)
    println("")
end print_sum

proc main()
    print_sum(5, 3)             // 8
end main

Parameters

Type annotations required:

fn calculate(x: int, y: int, z: int): int
    return x + y * z
end calculate

All parameters are immutable (passed by value).

Argument evaluation order

Reef does not promise a single argument evaluation order for every call. The rule is per call site:

  • At call sites the compiler instruments for GC argument rooting (any site where a GC-managed argument or receiver is evaluated before a later argument that may allocate — the common case for calls that build their arguments inline), arguments are evaluated strictly left to right, receiver first, then the listed arguments, then any defaulted parameters being filled in.
  • At all other call sites the evaluation order is unspecified (it follows the underlying C compiler's argument order).

Do not write call sites whose correctness depends on the evaluation order of side-effecting arguments: whether a given site is instrumented is a compiler implementation detail and can change between releases. The safe form is to bind side-effecting expressions with let first and pass the names.


Default Parameters

Parameters can have default values that are used when the argument is omitted at the call site.

Syntax

fn name(required: type, optional: type = default_value): return_type
    // ...
end name

Rules

  1. Default parameters must come after required parameters - You cannot have a required parameter after a default parameter.

  2. Default values must be compile-time expressions - Literals, constants, and simple expressions are supported.

  3. Type must match - The default value must be compatible with the parameter type.

Examples

Single default parameter:

fn greet(name: string, greeting: string = "Hello"): string
    return "${greeting}, ${name}!"
end greet

proc main()
    println(greet("World"))           // "Hello, World!"
    println(greet("World", "Hi"))     // "Hi, World!"
end main

Multiple default parameters:

fn format_number(n: int, prefix: string = "", suffix: string = ""): string
    return "${prefix}${n}${suffix}"
end format_number

proc main()
    println(format_number(42))                    // "42"
    println(format_number(42, "$"))               // "$42"
    println(format_number(42, "$", ".00"))        // "$42.00"
end main

All parameters with defaults:

fn make_greeting(text: string = "Hello", times: int = 1): string
    mut result: string = ""
    mut i: int = 0
    while i < times
        result = "${result}${text}\n"
        i = i + 1
    end while
    return result
end make_greeting

proc main()
    println(make_greeting())                      // "Hello\n"
    println(make_greeting("Hi"))                  // "Hi\n"
    println(make_greeting("Yo", 3))               // "Yo\nYo\nYo\n"
end main

Procedures with defaults:

proc log_message(msg: string, level: string = "INFO")
    println("[${level}] ${msg}")
end log_message

proc main()
    log_message("Starting...")                    // "[INFO] Starting..."
    log_message("Oops!", "ERROR")                 // "[ERROR] Oops!"
end main

Default Parameters with Generic Functions

Default parameters work with generic functions, both with explicit type arguments and type inference:

fn wrap[T](value: T, label: string = "value"): string
    return label
end wrap

proc main()
    // With explicit type argument
    println(wrap[:int](42))              // "value" (uses default)
    println(wrap[:int](42, "number"))    // "number"

    // With type inference (no [:T] needed)
    let x: int = 42
    println(wrap(x))                     // "value" (infers T=int)
    println(wrap(x, "num"))              // "num"
end main

See 055_GENERICS.md for more on generic functions.


Return Statements

Functions must return a value of the declared type:

fn get_value(): int
    return 42
end get_value

Procedures can use return for early exit:

proc process(value: int)
    if value < 0
        return           // Early exit, no value
    end if
    println("Processing...")
end process

Labeled Ends

fn calculate(x: int): int
    return x * 2
end calculate

Both end calculate and end fn are valid.


The Main Function

Every Reef program requires a main entry point. You can use either form:

Procedure (no exit code):

proc main()
    println("Hello, world!")
end main

Programs using proc main() always exit with code 0.

Function (with exit code):

fn main(): int
    if some_error
        return 1      // Exit with error
    end if
    return 0          // Exit with success
end main

Programs using fn main(): int return the specified exit code to the OS.

Immediate Exit: Use exit(code) to terminate immediately from anywhere:

fn validate(): bool
    if critical_error
        exit(1)       // Terminate program immediately
    end if
    return true
end validate

Next: 025_STRING_INTERPOLATION.md Previous: 015_BASICS.md