Functions
Basics · Lesson 13
Parameters always have types. The return type comes after ->. The last line, written without a semicolon, is the return value.
Coming from GoTypes come after names like Go, with -> i32 for the return type. The last expression without a semicolon is the return value, and return is still there for leaving early.
Coming from KotlinThe shape is Kotlin's fun add(a: Int, b: Int): Int, with -> before the return type. Braces are always required (there is no = a + b body), and the last expression without a semicolon is the result, so return is only for leaving early.
Coming from PythonLike a fully type-hinted def, except the compiler enforces the hints and you must write them for every parameter. The last expression without a semicolon is returned, so leaving out return doesn't give you None.
Coming from TypeScriptSame shape as function add(a: number, b: number): number, with -> before the return type. Parameter types must always be written, and so must any return type other than nothing. The last expression without a semicolon is the return value.
Coming from JavaThe return type goes after ->, and the last expression without a semicolon is the return value, so return is only needed for early exits. Rust has no overloading, so two functions need two different names.
Coming from C#The return type comes after ->, and a block's last expression (no semicolon) is its result. Unlike an expression-bodied => member, the block can hold statements first. Rust has no method overloading and no default or named arguments.
Coming from Cfn add(a: i32, b: i32) -> i32 is int add(int a, int b) with the return type moved to the end. The last expression without a semicolon is returned, and falling off the end of a function that should return a value is a compile error, not junk.
Coming from C++The trailing -> i32 is like C++'s trailing return type, and the last expression without ; is the return value. Forgetting to return a value is a compile error rather than undefined behaviour, and Rust has no overloading or default arguments.
The quick comparison
fn add(a: i32, b: i32) -> i32 { a + b // no semicolon: this is what the function returns}
fn greet(name: &str) { // no -> means it returns nothing println!("hi {name}");}
fn main() { println!("{}", add(2, 3)); greet("kotlin");}5 hi kotlin
package main
import "fmt"
func add(a, b int) int { return a + b}
func greet(name string) { fmt.Println("hi", name)}
func main() { fmt.Println(add(2, 3)) greet("kotlin")}5 hi kotlin
fun add(a: Int, b: Int): Int { return a + b}
fun greet(name: String) { println("hi $name")}
fun main() { println(add(2, 3)) greet("kotlin")}5 hi kotlin
def add(a: int, b: int) -> int: return a + b # return gives the value back
def greet(name: str) -> None: print(f"hi {name}")
print(add(2, 3))greet("kotlin")5 hi kotlin
function add(a: number, b: number): number { return a + b;}
function greet(name: string): void { // void: returns nothing console.log(`hi ${name}`);}
console.log(add(2, 3));greet("kotlin");5 hi kotlin
public class Main { static int add(int a, int b) { return a + b; }
static void greet(String name) { // void means it returns nothing System.out.println("hi " + name); }
public static void main(String[] args) { System.out.println(add(2, 3)); greet("kotlin"); }}5 hi kotlin
Console.WriteLine(Add(2, 3));Greet("kotlin");
static int Add(int a, int b) => a + b;
static void Greet(string name) // void: returns nothing{ Console.WriteLine($"hi {name}");}5 hi kotlin
#include <stdio.h>
int add(int a, int b) { return a + b; // return gives the value back}
void greet(const char *name) { // void means it returns nothing printf("hi %s\n", name);}
int main(void) { printf("%d\n", add(2, 3)); greet("kotlin"); return 0;}5 hi kotlin
#include <iostream>#include <string_view>
int add(int a, int b) { return a + b;}
void greet(std::string_view name) { // void: returns nothing std::cout << "hi " << name << "\n";}
int main() { std::cout << add(2, 3) << "\n"; greet("kotlin");}5 hi kotlin
What's different
return a + b;also works. The no-semicolon style is just shorter, and it is what most Rust code uses.&stris the type for text you only read; a string literal such as"kotlin"has it. Strings comes later.- Functions can be declared after they are used. Their order in the file doesn't matter.
- Arguments are passed by value, and a
Stringpassed in is moved, not copied (Ownership). There are no default or named arguments and no overloading (What Rust lacks).
A semicolon on the last line
Looks fine: in Python and C it runs but gives a wrong answer; in C++ it crashes at runtime. Rust won't compile it.
Go refuses this too. Same idea, different words.Kotlin refuses this too. Same idea, different words.Broken in Python. Rust catches it.TypeScript refuses this too. Same idea, different words.Java refuses this too. Same idea, different words.C# refuses this too. Same idea, different words.Broken in C. Rust catches it.Broken in C++. Rust catches it.fn add(a: i32, b: i32) -> i32 { a + b;}
fn main() { println!("{}", add(2, 3));}Go refuses this too: a + b is "not used" and the function has a missing return. Go always needs an explicit return, so a stray semicolon can't change what a function returns.
package main
import "fmt"
func add(a int, b int) int { a + b}
func main() { fmt.Println(add(2, 3))}# command-line-arguments ./main.go:6:2: a + b (value of type int) is not used ./main.go:7:1: missing return
Kotlin refuses this too: a block body needs return, so a body whose last line is just a + b gives missing return statement.
fun add(a: Int, b: Int): Int { a + b}
fun main() { println(add(2, 3))}Main.kt:3:1: error: missing return statement. } ^
def add(a: int, b: int) -> int: a + b
print(add(2, 3))None
TypeScript refuses this too: add promises a number but has no return. Without the : number it would compile and quietly return undefined.
function add(a: number, b: number): number { a + b;}
console.log(add(2, 3));main.ts(1,37): error TS2355: A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value.
Java refuses this too: a + b; is "not a statement". Java always needs an explicit return, so a stray semicolon can't change what a method returns.
public class Main { static int add(int a, int b) { a + b; }
public static void main(String[] args) { System.out.println(add(2, 3)); }}Main.java:3: error: not a statement
a + b;
^
1 errorC# refuses this too: a + b; is not a valid statement on its own, and the method never returns a value (CS0201, CS0161). C# always needs an explicit return.
Console.WriteLine(Add(2, 3));
static int Add(int a, int b){ a + b;}Program.cs(5,5): error CS0201: Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement Program.cs(3,12): error CS0161: 'Add(int, int)': not all code paths return a value
#include <stdio.h>
int add(int a, int b) { a + b;}
int main(void) { printf("%d\n", add(2, 3)); return 0;}main.c: In function ‘add’:
main.c:4:7: warning: statement with no effect [-Wunused-value]
4 | a + b;
| ~~^~~
main.c:5:1: warning: control reaches end of non-void function [-Wreturn-type]
5 | }
| ^2
#include <iostream>
int add(int a, int b) { a + b;}
int main() { std::cout << add(2, 3) << "\n";}main.cpp: In function ‘int add(int, int)’:
main.cpp:4:7: warning: statement has no effect [-Wunused-value]
4 | a + b;
| ~~^~~
main.cpp:5:1: warning: no return statement in function returning non-void [-Wreturn-type]
5 | }
| ^What does the compiler say? Guess first, then open.
error[E0308]: mismatched types --> src/main.rs:1:27 | 1 | fn add(a: i32, b: i32) -> i32 { | --- ^^^ expected `i32`, found `()` | | | implicitly returns `()` as its body has no tail or `return` expression 2 | a + b; | - help: remove this semicolon to return this value
Go refuses this too: a + b is "not used" and the function has a missing return. Go always needs an explicit return, so a stray semicolon can't change what a function returns.
Kotlin refuses this too: a block body needs return, so a body whose last line is just a + b gives missing return statement.
Python prints None. Without return, the last line is worked out and thrown away, and the function gives back None.
TypeScript refuses this too: add promises a number but has no return. Without the : number it would compile and quietly return undefined.
Java refuses this too: a + b; is "not a statement". Java always needs an explicit return, so a stray semicolon can't change what a method returns.
C# refuses this too: a + b; is not a valid statement on its own, and the method never returns a value (CS0201, CS0161). C# always needs an explicit return.
gcc only warns that a + b does nothing and that add never returns. The program prints 2 instead of 5: whatever value happened to be left behind. Another compiler or machine can print something else.
g++ warns that a + b; does nothing and that add never returns, then builds it anyway. Running off the end is undefined behaviour, and here g++ plants a trap so the program dies before printing. On ARM, this site's machine, that is a trap signal (SIGTRAP); on x86 it is an illegal-instruction signal (SIGILL).
In Rust a block's value is its last expression. Adding ; turns a + b into a statement that produces nothing, written () and called the unit type.
So the function now promises an i32 and gives back (). The compiler's help: line points at the exact semicolon to remove.
The fix
Compiles and runs.
fn add(a: i32, b: i32) -> i32 { a + b}
fn main() { println!("{}", add(2, 3));}5
The toolkit
A Rust function states its inputs and its output in its signature. How the body ends (the tail expression: the last line, with no ;) is in Statements and expressions, and reading a signature piece by piece is in Reading signatures. Outputs come from Linux on 64-bit ARM with Rust 1.99.1.
The signature is the contract
Each parameter is name: Type and the result follows -> (the spelling). No -> means the function returns (), the value for “nothing” (unit), and -> ! means it never returns (never type).
fn add(a: i32, b: i32) -> i32 { a + b}
fn greet(name: &str) { println!("hello, {name}");}
fn main() { println!("add(2, 3): {}", add(2, 3)); greet("Ferris");}add(2, 3): 5 hello, Ferris
Inside a body Rust works out types for you (Variables); in a signature the type is required for every parameter. Leave one out and the compiler complains at the comma. Grouping, as in a, b: i32, leaves a without one:
fn add(a, b: i32) -> i32 { a + b}
fn main() { println!("add(2, 3): {}", add(2, 3));}error: expected one of `:`, `@`, or `|`, found `,` --> src/main.rs:1:9 | 1 | fn add(a, b: i32) -> i32 { | ^ expected one of `:`, `@`, or `|` | help: if this is a parameter name, give it a type | 1 | fn add(a: TypeName, b: i32) -> i32 { | ++++++++++ help: if this is a type, explicitly ignore the parameter name | 1 | fn add(_: a, b: i32) -> i32 { | ++
Leave the return type out and it is (), so a body that produces a number is a mismatch. The compiler’s help line suggests the type:
fn add(a: i32, b: i32) { a + b}
fn main() { add(2, 3);}error[E0308]: mismatched types --> src/main.rs:2:5 | 1 | fn add(a: i32, b: i32) { | - help: try adding a return type: `-> i32` 2 | a + b | ^^^^^ expected `()`, found `i32`
Leaving early
A function normally ends on its tail expression; return is for leaving before the end, from inside an if or a loop:
fn sign(n: i32) -> i32 { if n < 0 { return -1; } if n == 0 { return 0; } 1}
fn next_multiple(of: u32, above: u32) -> u32 { let mut n = above + 1; loop { if n % of == 0 { return n; } n += 1; }}
fn main() { println!("sign(-7): {}", sign(-7)); println!("sign(0): {}", sign(0)); println!("sign(12): {}", sign(12)); println!("next_multiple(7, 20): {}", next_multiple(7, 20));}sign(-7): -1 sign(0): 0 sign(12): 1 next_multiple(7, 20): 21
A function that panics or ends the program is written -> !, and that type fits anywhere a value is expected, which is why half can still promise an i32. The panic message names the line, and the exit status is non-zero (Errors covers panic! and its alternatives):
fn fail(msg: &str) -> ! { panic!("{msg}")}
fn half(n: i32) -> i32 { if n % 2 == 0 { n / 2 } else { fail("odd number") }}
fn main() { println!("half(10): {}", half(10)); println!("half(7): {}", half(7));}half(10): 5 thread 'main' (1) panicked at src/main.rs:2:5: odd number note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Functions inside functions
A function can be written inside another function’s body:
fn describe(n: i32) -> String { fn is_even(n: i32) -> bool { n % 2 == 0 } let kind = if is_even(n) { "even" } else { "odd" }; format!("{n} is {kind}")}
fn main() { println!("{}", describe(7)); println!("{}", describe(10));}7 is odd 10 is even
Because a nested function is an ordinary function, it cannot see the outer function’s variables. A closure can (Closures):
fn total_with_tax(price: u32) -> u32 { let rate = 20; fn tax(p: u32) -> u32 { p * rate / 100 } println!("rate: {rate}"); price + tax(price)}
fn main() { println!("total: {}", total_with_tax(50));}error[E0434]: can't capture dynamic environment in a fn item --> src/main.rs:4:13 | 4 | p * rate / 100 | ^^^^ | = help: use the `|| { ... }` closure form instead
fn total_with_tax(price: u32) -> u32 { let rate = 20; let tax = |p: u32| p * rate / 100; println!("rate: {rate}"); price + tax(price)}
fn main() { println!("total: {}", total_with_tax(50));}rate: 20 total: 60
Arguments are passed by value
Every argument is handed to the parameter: copied for simple values such as numbers, moved for others. The parameter is a new variable: changing it needs mut, and does not change the caller’s. A let with the same name hides it (shadowing):
fn bump(mut n: i32) -> i32 { n += 1; n}
fn cap(qty: u32) -> u32 { let qty = qty.min(10); // a new variable that hides the parameter qty * 3}
fn main() { let a = 5; let b = bump(a); println!("a: {a}, b: {b}"); println!("cap(4): {}", cap(4)); println!("cap(50): {}", cap(50));}a: 5, b: 6 cap(4): 12 cap(50): 30
A String is not copied for you. Passing it gives it away, and the caller cannot use it afterwards:
fn length(s: String) -> usize { s.len()}
fn main() { let name = String::from("Ferris"); let n = length(name); println!("{n} {name}");}error[E0382]: borrow of moved value: `name` --> src/main.rs:8:20 | 6 | let name = String::from("Ferris"); | ---- move occurs because `name` has type `String`, which does not implement the `Copy` trait 7 | let n = length(name); | ---- value moved here 8 | println!("{n} {name}"); | ^^^^ value borrowed here after move | note: consider changing this parameter type in function `length` to borrow instead if owning the value isn't necessary --> src/main.rs:1:14 | 1 | fn length(s: String) -> usize { | ------ ^^^^^^ this parameter takes ownership of the value | | | in this function help: consider cloning the value if the performance cost is acceptable | 7 | let n = length(name.clone()); | ++++++++
That is the ownership rule, taught in Ownership (which values are copied is in Copy or reference?). The usual fix is to pass &name, a borrow (Borrowing), so the caller keeps the value.
Functions are values
A function’s name converts to a function pointer, whose type is written fn(i32) -> i32. A closure that captures nothing converts to one too:
fn double(n: i32) -> i32 { n * 2}
fn negate(n: i32) -> i32 { -n}
fn apply(f: fn(i32) -> i32, x: i32) -> i32 { f(x)}
fn main() { println!("apply(double, 4): {}", apply(double, 4)); println!("apply(negate, 4): {}", apply(negate, 4)); // A closure that captures nothing converts to a fn pointer. println!("apply(|n| n + 100, 4): {}", apply(|n| n + 100, 4));
let ops: [fn(i32) -> i32; 2] = [double, negate]; for f in ops { println!("f(10): {}", f(10)); }}apply(double, 4): 8 apply(negate, 4): -4 apply(|n| n + 100, 4): 104 f(10): 20 f(10): -10
A closure that uses a variable from outside does not:
fn apply(f: fn(i32) -> i32, x: i32) -> i32 { f(x)}
fn main() { let extra = 100; println!("{}", apply(|n| n + extra, 4));}error[E0308]: mismatched types --> src/main.rs:7:26 | 7 | println!("{}", apply(|n| n + extra, 4)); | ----- ^^^^^^^^^^^^^ expected fn pointer, found closure | | | arguments to this function are incorrect | = note: expected fn pointer `fn(i32) -> i32` found closure `{closure@src/main.rs:7:26: 7:29}` note: closures can only be coerced to `fn` types if they do not capture any variables --> src/main.rs:7:34 | 7 | println!("{}", apply(|n| n + extra, 4)); | ^^^^^ `extra` captured here note: function defined here --> src/main.rs:1:4 | 1 | fn apply(f: fn(i32) -> i32, x: i32) -> i32 { | ^^^^^ -----------------
To accept those too, take impl Fn(i32) -> i32 (below). Lowercase fn(...) is a type, capitalised Fn a bound: Reading signatures lists both.
Your habits, in Rust
Going deeper
impl Trait as a parameter type
impl Fn(i32) -> i32 or impl Display means “any type that can do this” (how to read it). The Reference calls it an anonymous type parameter: the caller picks the type, the body may use only what the trait provides. A closure that captures works here, and so does a number or a string. See Traits and Generics.
use std::fmt::Display;
fn apply(f: impl Fn(i32) -> i32, x: i32) -> i32 { f(x)}
fn show(x: impl Display) -> String { format!("<{x}>")}
fn main() { let extra = 100; println!("apply(|n| n + extra, 4): {}", apply(|n| n + extra, 4)); println!("show(5): {}", show(5)); println!("show(\"hi\"): {}", show("hi"));}apply(|n| n + extra, 4): 104
show(5): <5>
show("hi"): <hi>const fn
A const fn can be run by the compiler while compiling, when it is called in a const, a static or an array length (Declarations and types). It can still be called at run time. A const fn may call only other const fns (and tuple-struct constructors):
const fn square(n: u32) -> u32 { n * n}
const AREA: u32 = square(12);static GRID: [u8; square(3) as usize] = [0; square(3) as usize];
fn main() { println!("AREA: {AREA}"); println!("GRID.len(): {}", GRID.len()); println!("square(7) at run time: {}", square(7));}AREA: 144 GRID.len(): 9 square(7) at run time: 49
fn helper() -> u32 { 1}
const fn f() -> u32 { helper()}
fn main() { println!("{}", f());}error[E0015]: cannot call non-const function `helper` in constant functions --> src/main.rs:6:5 | 6 | helper() | ^^^^^^^^ | note: function `helper` is not const --> src/main.rs:1:1 | 1 | fn helper() -> u32 { | ^^^^^^^^^^^^^^^^^^ = note: calls in constant functions are limited to constant functions, tuple structs and tuple variants
The C++ cousin is constexpr. The Reference lists what constant evaluation allows.
Deep recursion
A function can call itself. Each call takes space on the thread’s stack (Stack and heap), which is fixed in size, so a call chain that is too deep runs out:
fn depth(n: u64) -> u64 { if n == 0 { 0 } else { 1 + depth(n - 1) }}
fn main() { println!("depth(1_000): {}", depth(1_000)); println!("depth(100_000_000): {}", depth(100_000_000));}depth(1_000): 1000 thread 'main' (1) has overflowed its stack fatal runtime error: stack overflow, aborting
The depth that fails depends on the machine and the stack size, and these are debug builds: an optimised build can change the outcome for a function this simple. Rust prints a message and aborts the process. Some languages turn the same bug into an exception, and others die as Rust does:
Practice
Try it
Each exercise is a small program with checks in main. Open it in the Playground, replace the todo!(), and run it until it prints all checks passed.
Small pure functions
Write three functions. Use an early return in two and a plain tail expression in the other, make gcd call itself, and mind the semicolons (the gotcha above is the trap). Leap years (1900 and 2000) and a zero on either side of gcd are what people get wrong.
/// `n` limited to the range `lo..=hi`.fn clamp(n: i32, lo: i32, hi: i32) -> i32 { todo!("return lo if n < lo, hi if n > hi, otherwise n: an early return, then a tail expression")}
/// Gregorian leap year: divisible by 4, except centuries, except every 400th year.fn is_leap_year(year: u32) -> bool { todo!("one boolean expression with && and ||, and no semicolon at the end")}
/// The greatest common divisor, by Euclid's rule: gcd(a, 0) is a, otherwise gcd(b, a % b).fn gcd(a: u32, b: u32) -> u32 { todo!("the base case is an early return; otherwise the function calls itself")}
fn main() { assert_eq!(clamp(5, 0, 10), 5); assert_eq!(clamp(-3, 0, 10), 0); assert_eq!(clamp(99, 0, 10), 10); assert_eq!(clamp(0, 0, 10), 0);
assert!(is_leap_year(2024)); assert!(!is_leap_year(2023)); // Centuries are not leap years, unless divisible by 400. assert!(!is_leap_year(1900)); assert!(is_leap_year(2000));
assert_eq!(gcd(12, 18), 6); assert_eq!(gcd(17, 5), 1); // The base case, whichever side is zero. assert_eq!(gcd(7, 0), 7); assert_eq!(gcd(0, 5), 5);
println!("all checks passed");}Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.
Show a solution
/// `n` limited to the range `lo..=hi`.fn clamp(n: i32, lo: i32, hi: i32) -> i32 { if n < lo { return lo; } if n > hi { return hi; } n}
/// Gregorian leap year: divisible by 4, except centuries, except every 400th year.fn is_leap_year(year: u32) -> bool { year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)}
/// The greatest common divisor, by Euclid's rule: gcd(a, 0) is a, otherwise gcd(b, a % b).fn gcd(a: u32, b: u32) -> u32 { if b == 0 { return a; } gcd(b, a % b)}
fn main() { assert_eq!(clamp(5, 0, 10), 5); assert_eq!(clamp(-3, 0, 10), 0); assert_eq!(clamp(99, 0, 10), 10); assert_eq!(clamp(0, 0, 10), 0);
assert!(is_leap_year(2024)); assert!(!is_leap_year(2023)); // Centuries are not leap years, unless divisible by 400. assert!(!is_leap_year(1900)); assert!(is_leap_year(2000));
assert_eq!(gcd(12, 18), 6); assert_eq!(gcd(17, 5), 1); // The base case, whichever side is zero. assert_eq!(gcd(7, 0), 7); assert_eq!(gcd(0, 5), 5);
println!("all checks passed");}all checks passed
Functions as values
by_name returns one of three functions, chosen by a match. apply_n takes impl Fn(i32) -> i32 and applies it repeatedly, so a closure that captures a variable works. Applying it zero times must give back the input.
fn double(n: i32) -> i32 { n * 2}
fn negate(n: i32) -> i32 { -n}
fn identity(n: i32) -> i32 { n}
/// The function called `name`: "double", "negate", or `identity` for anything else.fn by_name(name: &str) -> fn(i32) -> i32 { todo!("a match on name whose arms are function names, with no parentheses")}
/// Apply `f` to `x`, then to the result, `times` times in all. `f` may capture variables.fn apply_n(f: impl Fn(i32) -> i32, times: u32, x: i32) -> i32 { todo!("a for loop over 0..times that updates a mutable copy of x")}
fn main() { assert_eq!(by_name("double")(4), 8); assert_eq!(by_name("negate")(4), -4); assert_eq!(by_name("nope")(4), 4);
assert_eq!(apply_n(double, 3, 1), 8); assert_eq!(apply_n(negate, 2, 7), 7); // Zero times means the input comes back unchanged. assert_eq!(apply_n(double, 0, 5), 5); // A closure that captures `step` works because the parameter is `impl Fn`. let step = 7; assert_eq!(apply_n(|n| n + step, 3, 0), 21);
println!("all checks passed");}Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.
Show a solution
fn double(n: i32) -> i32 { n * 2}
fn negate(n: i32) -> i32 { -n}
fn identity(n: i32) -> i32 { n}
/// The function called `name`: "double", "negate", or `identity` for anything else.fn by_name(name: &str) -> fn(i32) -> i32 { match name { "double" => double, "negate" => negate, _ => identity, }}
/// Apply `f` to `x`, then to the result, `times` times in all. `f` may capture variables.fn apply_n(f: impl Fn(i32) -> i32, times: u32, x: i32) -> i32 { let mut value = x; for _ in 0..times { value = f(value); } value}
fn main() { assert_eq!(by_name("double")(4), 8); assert_eq!(by_name("negate")(4), -4); assert_eq!(by_name("nope")(4), 4);
assert_eq!(apply_n(double, 3, 1), 8); assert_eq!(apply_n(negate, 2, 7), 7); // Zero times means the input comes back unchanged. assert_eq!(apply_n(double, 0, 5), 5); // A closure that captures `step` works because the parameter is `impl Fn`. let step = 7; assert_eq!(apply_n(|n| n + step, 3, 0), 21);
println!("all checks passed");}all checks passed
Read more
- The Rust Book, chapter 3.3: Functions, and chapter 20.4: Advanced Functions and Closures for function pointers.
- Rust by Example: Functions.
- The Rust Reference: Functions, Function pointer types, Impl trait type and Constant evaluation.
- The primitive type
fnand the never type in the standard library docs.
Learn Rust is an independent project, not affiliated with the Rust Project or the Rust Foundation. Code and text are under the MIT licence.Source on GitHub.