Functions
fun add(a: i64, b: i64) -> i64 {
return a + b;
}
fun main() -> i64 {
return add(2, 3);
}
Parameter type annotations are optional when types can be inferred from context. The return type follows -> and is also optional — a function with no return annotation and no return expr; returns (). return expr; and bare return; are both valid.
Associated Functions
impl blocks may contain functions with no self parameter. These are called on the type via :: syntax and serve as the canonical constructor pattern:
struct Point {
x: f64,
y: f64,
}
impl Point {
fun new(x: f64, y: f64) -> Point {
return Point { x: x, y: y };
}
}
fun main() -> i64 {
let p = Point::new(1.0, 2.0);
return p.x as i64;
}
First-Class Functions
Functions are first-class values and can be assigned, passed, and returned:
fun add(a: i64, b: i64) -> i64 {
return a + b;
}
fun apply(f: (i64) -> i64, x: i64) -> i64 {
return f(x);
}
fun main() -> i64 {
let f = add;
let inc = (x: i64) -> i64 { return x + 1; };
return f(1, 2) + apply(inc, 4);
}
The type of a function or closure is written as (ParamTypes) -> ReturnType.
Closures
Anonymous functions are written with the (...) -> ... { ... } form:
fun main() -> i64 {
let double = (x: i64) -> i64 { return x * 2; };
return double(5);
}
Closures capture variables from their enclosing scope by value. A captured variable is cloned into the closure environment when the closure is created:
fun main() -> i64 {
let mut count = 0;
let inc = () -> { count += 1; };
inc();
inc();
return count; // still 0
}
Shared mutable closure state is explicit. If multiple closures must observe and update the same non-linear storage, the program must capture a regular pointer:
fun main() -> i64 {
let mut count = 0;
let p: *mut i64 = &mut count;
let inc = () -> { *p += 1; };
inc();
inc();
return *p;
}
Turbofish
Availability: Since v0.8.0.
When a generic function's type parameters cannot be inferred from the arguments, they can be specified explicitly with turbofish syntax: name::<T, U>(args).
fun identity<T>(x: T) -> T { x }
fun main() -> i64 {
let x = identity::<i64>(42);
return x;
}
Turbofish is most useful when two or more independent type parameters must be pinned at the call site — for example, a zip function that pairs elements from arrays of different types:
fun zip<A, B>(a: A[], b: B[]) -> (A, B)[] { /* ... */ }
fun main() {
let pairs = zip::<i64, String>([1, 2], ["a", "b"]);
}
Type ascription (: T) remains available for annotating the result type. Turbofish and ascription can be used together:
let result = parse::<i64>("42") : Perhaps<i64>;
The ? Operator
Availability:
?with matching error types: since v0.1.0.?withFrom-based error coercion: since v0.4.0.
Inside a function returning Result<T, E>, ? propagates errors early:
fun parse_int(s: String) -> Result<i64, String> {
if (s == "21") {
return Result::Ok { value: 21 };
}
return Result::Err { error: "not a number" };
}
fun parse_and_double(s: String) -> Result<i64, String> {
let n = parse_int(s)?; // returns Err early if parse_int fails
return Result::Ok { value: n * 2 };
}
fun main() -> i64 {
match parse_and_double("21") {
Result::Ok { value } => value,
Result::Err { error } => 0,
}
}
? desugars to: if the expression is Err(e), return Err(E2::from(e)) immediately (where E2 is the enclosing function's error type); otherwise unwrap to the Ok value.
The inner expression's error type E1 and the function's return error type E2 must satisfy E2: From<E1>. When E1 == E2 no conversion is performed. When they differ, From::from is called automatically on the error value before re-wrapping in Err.