Skip to content

Latest commit

 

History

History
157 lines (113 loc) · 3.6 KB

File metadata and controls

157 lines (113 loc) · 3.6 KB

Functions

A function has a name, parameters with type annotations, an optional return type, and a body.

fn add(a: int, b: int) -> int {
  a + b
}

fn greet(name: string) {
  fmt.Println(f"Hello, {name}")
}

Parameter types are required. The return type can be omitted; if so it defaults to ().

The last expression in the function body is the return value. Use return for early exits.

fn first_positive(nums: Slice<int>) -> Option<int> {
  for n in nums {
    if n > 0 {
      return Some(n)
    }
  }
  None
}

Generic functions

Type parameters appear in angle brackets after the function name.

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

fn swap<A, B>(pair: (A, B)) -> (B, A) {
  (pair.1, pair.0)
}

The compiler infers type arguments at call sites:

let x = identity(42)         // T = int
let y = identity("hello")    // T = string

Explicit type arguments are needed when inference has nothing to work with:

let empty = Slice.new<int>()
let counts = Map.new<string, int>()

Type bounds

A type parameter can be constrained to types that implement an interface. See 11-interfaces.md

fn print_value<T: Display>(value: T) {
  fmt.Println(value.to_string())
}

Multiple bounds use +:

fn process<T: Display + Clone>(value: T) -> T {
  fmt.Println(value.to_string())
  value.clone()
}

Multiple type parameters can each have their own bounds:

fn combine<T: Display, U: Debug>(a: T, b: U) -> string {
  a.to_string() + b.debug_string()
}

Two bounds are built in:

  • Comparable for types that admit == and != (everything except slices, maps, functions, and structs or tuples that contain them)
  • Ordered for types that admit <, >, <=, and >= (signed and unsigned integers, floats, and string)

Ordered implies Comparable, so == and != are also available on a type bound by Ordered.

fn dedupe<T: Comparable>(xs: Slice<T>) -> Slice<T> { ... }
fn sorted<T: Ordered>(xs: Slice<T>) -> Slice<T> { ... }

Mutable parameters

By default, parameters disallow rebinding inside the function body. Mark them mut to allow rebinding.

Additionally, if the function writes through the parameter in a way observable to the caller, marking the parameter mut requires the call-site binding to be mut as well.

fn sort_in_place(mut items: Slice<int>) {
  // ...
}

let mut nums = [3, 1, 2] // arg mutable
sort_in_place(nums)  // param mutable, ok

let nums = [3, 1, 2] // arg immutable
sort_in_place(nums)  // param immutable, error

This additional rule applies to Slice<T>, Map<K, V>, and any struct, tuple, or enum that recursively contains one. This rule does not apply to:

  • Values that Go passes by copy, e.g. int, string, bool, float64, plain structs, tuples.
  • Ref<T> and Channel<T>. The purpose of pointers and channels is to share or transmit data, so mutation is implied.

Lambdas

Anonymous functions whose params appear between | pipes.

let double = |x: int| x * 2
let sum = |a: int, b: int| a + b
let produce_int = || 42

Lambda parameter types can be omitted when inferable:

let doubled = [1, 2, 3].map(|x| x * 2)

A block body allows multiple statements:

let process = |x: int| {
  let y = x * 2
  y + 1
}

Lambdas capture variables from the enclosing scope:

let multiplier = 3
let scale = |x: int| x * multiplier

04-control-flow.md 06-structs-and-enums.md