Skip to content
Learn Rust
Theme

Reading a function signature

Syntax · Lesson 5

A function's signature is everything before its body: the name, the parameters, the result type, and any conditions on generic types. In Rust it packs in a lot, because it also says what a function may change, whether it can fail, and what its generic types must be able to do. This page reads real signatures from the standard library, piece by piece, and shows the use, pub and :: syntax that finds them.

Coming from Gofunc Largest[T cmp.Ordered](list []T) T becomes fn largest<T: PartialOrd>(list: &[T]) -> &T: the type parameter and its bound go in <> after the name, and the result follows ->. Go's int is 32 or 64 bits by platform (64 on the runner), so write i64 to pin it, or isize to follow the platform. The & in &[T] borrows the list (a view of its items) instead of taking it.

Coming from Kotlinfun <T : Comparable<T>> largest(list: List<T>): T becomes fn largest<T: PartialOrd>(list: &[T]) -> &T. The type parameter moves after the name, -> replaces the : before the result, and the & in &[T] borrows the list (a view of its items) instead of taking it.

Coming from PythonA hinted signature like def largest(items: Sequence[T]) -> T becomes fn largest<T: PartialOrd>(list: &[T]) -> &T. The interpreter ignores hints; the Rust compiler checks every piece before the program runs. PartialOrd plays the part of the Comparable protocol in the main program, and a call with a type that lacks it is a compile error.

Coming from TypeScriptfunction largest<T extends number | string>(list: T[]): T becomes fn largest<T: PartialOrd>(list: &[T]) -> &T. extends turns into a colon, and the bound names an ability (a trait) such as comparing with >, not a union of types. Breaking the bound is a compile error, as it is in TypeScript.

Coming from Javastatic <T extends Comparable<T>> T largest(List<T> list) becomes fn largest<T: PartialOrd>(list: &[T]) -> &T. The type parameter follows the name, extends turns into a colon, and the result follows ->. The & in &[T] borrows the list (a view of its items) instead of taking it.

Coming from C#static T Largest<T>(IReadOnlyList<T> list) where T : IComparable<T> becomes fn largest<T: PartialOrd>(list: &[T]) -> &T. Rust has a where clause too, but a short bound sits inside the <>. The result follows ->, and the & in &[T] borrows the list (a view of its items).

Coming from CC has no generic functions (_Generic only chooses between per-type ones), so int largest_int(const int *list, int n) is written once per type. Rust writes one fn largest<T: PartialOrd>(list: &[T]) -> &T: the [T] slice knows its own length, and & is a reference, a pointer the compiler checks.

Coming from C++template <std::totally_ordered T> const T& largest(const std::vector<T>&) becomes fn largest<T: PartialOrd>(list: &[T]) -> &T. A trait bound is the nearest thing to a C++20 concept, but a type meets a trait only by declaring it. &T is like const T&, and &[T] accepts an array, a Vec or part of one.

The quick comparison

Rustsrc/main.rsRun ↗
fn largest<T: PartialOrd>(list: &[T]) -> &T {
let mut best = &list[0];
for item in list {
if item > best {
best = item;
}
}
best
}
fn main() {
let numbers = [3, 9, 4];
let words = ["pear", "fig", "apple"];
println!("largest number: {}", largest(&numbers));
println!("largest word: {}", largest(&words));
}
$ cargo run
largest number: 9
largest word: pear
package main
import (
"cmp"
"fmt"
)
func largest[T cmp.Ordered](list []T) T {
best := list[0]
for _, item := range list {
if item > best {
best = item
}
}
return best
}
func main() {
numbers := []int{3, 9, 4}
words := []string{"pear", "fig", "apple"}
fmt.Println("largest number:", largest(numbers))
fmt.Println("largest word:", largest(words))
}
$ go run main.go
largest number: 9
largest word: pear
fun <T : Comparable<T>> largest(list: List<T>): T {
var best = list[0]
for (item in list) {
if (item > best) {
best = item
}
}
return best
}
fun main() {
val numbers = listOf(3, 9, 4)
val words = listOf("pear", "fig", "apple")
println("largest number: ${largest(numbers)}")
println("largest word: ${largest(words)}")
}
$ kotlinc Main.kt -include-runtime -d main.jar && java -jar main.jar
largest number: 9
largest word: pear
from typing import Any, Protocol, Sequence, TypeVar
class Comparable(Protocol):
def __gt__(self, other: Any) -> bool: ...
T = TypeVar("T", bound=Comparable)
def largest(items: Sequence[T]) -> T:
best = items[0]
for item in items:
if item > best:
best = item
return best
numbers = [3, 9, 4]
words = ["pear", "fig", "apple"]
print("largest number:", largest(numbers))
print("largest word:", largest(words))
$ python3 main.py
largest number: 9
largest word: pear
function largest<T extends number | string>(list: T[]): T {
let best = list[0];
for (const item of list) {
if (item > best) {
best = item;
}
}
return best;
}
const numbers = [3, 9, 4];
const words = ["pear", "fig", "apple"];
console.log("largest number:", largest(numbers));
console.log("largest word:", largest(words));
$ tsc --strict --module nodenext --types node main.ts && node main.js
largest number: 9
largest word: pear
import java.util.List;
public class Main {
static <T extends Comparable<T>> T largest(List<T> list) {
T best = list.get(0);
for (T item : list) {
if (item.compareTo(best) > 0) {
best = item;
}
}
return best;
}
public static void main(String[] args) {
List<Integer> numbers = List.of(3, 9, 4);
List<String> words = List.of("pear", "fig", "apple");
System.out.println("largest number: " + largest(numbers));
System.out.println("largest word: " + largest(words));
}
}
$ javac Main.java && java Main
largest number: 9
largest word: pear
using System;
using System.Collections.Generic;
static T Largest<T>(IReadOnlyList<T> list) where T : IComparable<T>
{
T best = list[0];
foreach (T item in list)
{
if (item.CompareTo(best) > 0)
{
best = item;
}
}
return best;
}
var numbers = new List<int> { 3, 9, 4 };
var words = new List<string> { "pear", "fig", "apple" };
Console.WriteLine($"largest number: {Largest(numbers)}");
Console.WriteLine($"largest word: {Largest(words)}");
$ dotnet run
largest number: 9
largest word: pear
#include <stdio.h>
#include <string.h>
/* C has no generics: one function per type. */
static int largest_int(const int *list, int n) {
int best = list[0];
for (int i = 0; i < n; i++) {
if (list[i] > best) {
best = list[i];
}
}
return best;
}
static const char *largest_str(const char **list, int n) {
const char *best = list[0];
for (int i = 0; i < n; i++) {
if (strcmp(list[i], best) > 0) {
best = list[i];
}
}
return best;
}
int main(void) {
int numbers[] = {3, 9, 4};
const char *words[] = {"pear", "fig", "apple"};
printf("largest number: %d\n", largest_int(numbers, 3));
printf("largest word: %s\n", largest_str(words, 3));
return 0;
}
$ gcc -Wall -Wextra main.c -o main && ./main
largest number: 9
largest word: pear
#include <concepts>
#include <iostream>
#include <string>
#include <vector>
template <std::totally_ordered T>
const T& largest(const std::vector<T>& list) {
const T* best = &list[0];
for (const T& item : list) {
if (item > *best) {
best = &item;
}
}
return *best;
}
int main() {
std::vector<int> numbers{3, 9, 4};
std::vector<std::string> words{"pear", "fig", "apple"};
std::cout << "largest number: " << largest(numbers) << "\n";
std::cout << "largest word: " << largest(words) << "\n";
}
$ g++ -std=c++20 -Wall -Wextra main.cpp -o main && ./main
largest number: 9
largest word: pear

What's different

  • A generic function lists its type parameters in <> after its name. T: PartialOrd is a bound: it says a T must be able to be compared with > and <, and + joins several bounds.
  • &T is a borrowed look at a T, &mut T a borrow that may change it, and a bare T hands the value over (small types such as integers are copied instead). The signature tells you which.
  • The result follows ->. Option<&T> means "maybe a reference", and Result<T, E> means "a T, or an error E", so a signature says whether a call can come back empty or fail.
  • Read a long signature in this order: the name, the parameters, the result, and the bounds last. :: walks a path such as std::cmp::max, and use brings a name in so you can write it short.
Gotcha

Comparing a type that has no bound

Looks fine: in Python, TypeScript, C and C++ it runs fine. Rust won't compile it.

Go refuses this too. Same idea, different words.Kotlin refuses this too. Same idea, different words.Works in Python. In Rust it's just a rule.Works in TypeScript. In Rust it's just a rule.Java refuses this too. Same idea, different words.C# refuses this too. Same idea, different words.Works in C. In Rust it's just a rule.Works in C++. In Rust it's just a rule.
Rustsrc/main.rs✗ won't compile
fn largest<T>(list: &[T]) -> &T {
let mut best = &list[0];
for item in list {
if item > best {
best = item;
}
}
best
}
fn main() {
let numbers = [3, 9, 4];
let words = ["pear", "fig", "apple"];
println!("largest number: {}", largest(&numbers));
println!("largest word: {}", largest(&words));
}
Go✗ refuses too

Go refuses: with [T any], > is not allowed, because any includes types that cannot be ordered. Go needs [T cmp.Ordered], which plays the part of T: PartialOrd but is a fixed list of built-in types; a Rust trait can be implemented for your own types too.

package main
import (
"fmt"
)
func largest[T any](list []T) T {
best := list[0]
for _, item := range list {
if item > best {
best = item
}
}
return best
}
func main() {
numbers := []int{3, 9, 4}
words := []string{"pear", "fig", "apple"}
fmt.Println("largest number:", largest(numbers))
fmt.Println("largest word:", largest(words))
}
# command-line-arguments
./main.go:10:6: invalid operation: item > best (type parameter T cannot use operator >)
Kotlin✗ refuses too

Kotlin refuses: an unbounded T has no >. Write <T : Comparable<T>>.

fun <T> largest(list: List<T>): T {
var best = list[0]
for (item in list) {
if (item > best) {
best = item
}
}
return best
}
fun main() {
val numbers = listOf(3, 9, 4)
val words = listOf("pear", "fig", "apple")
println("largest number: ${largest(numbers)}")
println("largest word: ${largest(words)}")
}
Main.kt:4:18: error: candidate 'fun String.compareTo(other: String, ignoreCase: Boolean = ...): Int' is inapplicable because of a receiver type mismatch.
        if (item > best) {
                 ^
def largest(items):
best = items[0]
for item in items:
if item > best:
best = item
return best
numbers = [3, 9, 4]
words = ["pear", "fig", "apple"]
print("largest number:", largest(numbers))
print("largest word:", largest(words))
$ python3 main.py✓ runs
largest number: 9
largest word: pear
function largest<T>(list: T[]): T {
let best = list[0];
for (const item of list) {
if (item > best) {
best = item;
}
}
return best;
}
const numbers = [3, 9, 4];
const words = ["pear", "fig", "apple"];
console.log("largest number:", largest(numbers));
console.log("largest word:", largest(words));
$ tsc --strict --module nodenext --types node main.ts && node main.js✓ runs
largest number: 9
largest word: pear
Java✗ refuses too

Java refuses: > works only on numeric types, so a generic needs T extends Comparable<T> and compareTo.

import java.util.List;
public class Main {
static <T> T largest(List<T> list) {
T best = list.get(0);
for (T item : list) {
if (item > best) {
best = item;
}
}
return best;
}
public static void main(String[] args) {
List<Integer> numbers = List.of(3, 9, 4);
List<String> words = List.of("pear", "fig", "apple");
System.out.println("largest number: " + largest(numbers));
System.out.println("largest word: " + largest(words));
}
}
Main.java:7: error: bad operand types for binary operator '>'
            if (item > best) {
                     ^
  first type:  T
  second type: T
  where T is a type-variable:
    T extends Object declared in method <T>largest(List<T>)
1 error
C#✗ refuses too

C# refuses (CS0019): add where T : IComparable<T> and call CompareTo.

using System;
using System.Collections.Generic;
static T Largest<T>(IReadOnlyList<T> list)
{
T best = list[0];
foreach (T item in list)
{
if (item > best)
{
best = item;
}
}
return best;
}
var numbers = new List<int> { 3, 9, 4 };
var words = new List<string> { "pear", "fig", "apple" };
Console.WriteLine($"largest number: {Largest(numbers)}");
Console.WriteLine($"largest word: {Largest(words)}");
Program.cs(9,13): error CS0019: Operator '>' cannot be applied to operands of type 'T' and 'T'
#include <stdio.h>
#include <string.h>
/* C has no generics: one function per type. */
static int largest_int(const int *list, int n) {
int best = list[0];
for (int i = 0; i < n; i++) {
if (list[i] > best) {
best = list[i];
}
}
return best;
}
static const char *largest_str(const char **list, int n) {
const char *best = list[0];
for (int i = 0; i < n; i++) {
if (strcmp(list[i], best) > 0) {
best = list[i];
}
}
return best;
}
int main(void) {
int numbers[] = {3, 9, 4};
const char *words[] = {"pear", "fig", "apple"};
printf("largest number: %d\n", largest_int(numbers, 3));
printf("largest word: %s\n", largest_str(words, 3));
return 0;
}
$ gcc -Wall -Wextra main.c -o main && ./main✓ runs
largest number: 9
largest word: pear
#include <iostream>
#include <string>
#include <vector>
template <typename T>
const T& largest(const std::vector<T>& list) {
const T* best = &list[0];
for (const T& item : list) {
if (item > *best) {
best = &item;
}
}
return *best;
}
int main() {
std::vector<int> numbers{3, 9, 4};
std::vector<std::string> words{"pear", "fig", "apple"};
std::cout << "largest number: " << largest(numbers) << "\n";
std::cout << "largest word: " << largest(words) << "\n";
}
$ g++ -std=c++20 -Wall -Wextra main.cpp -o main && ./main✓ runs
largest number: 9
largest word: pear
What does the compiler say? Guess first, then open.
$ cargo buildWhat is E0369?
error[E0369]: binary operation `>` cannot be applied to type `&T`
 --> src/main.rs:4:17
  |
4 |         if item > best {
  |            ---- ^ ---- &T
  |            |
  |            &T
  |
help: consider restricting type parameter `T` with trait `PartialOrd`
  |
1 | fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> &T {
  |             ++++++++++++++++++++++
What happens in Go

Go refuses: with [T any], > is not allowed, because any includes types that cannot be ordered. Go needs [T cmp.Ordered], which plays the part of T: PartialOrd but is a fixed list of built-in types; a Rust trait can be implemented for your own types too.

What happens in Kotlin

Kotlin refuses: an unbounded T has no >. Write <T : Comparable<T>>.

What happens in Python

Python prints the same. Nothing in largest says what it needs: > is tried on whatever arrives, and a list that cannot be ordered, such as [3, "nine", 4], raises a TypeError only when that call runs.

What happens in TypeScript

TypeScript compiles this under strict and prints the same: it asks only that both sides of > have the same type, not that the type can be ordered. So largest on objects compiles and quietly returns the first one. The extends number | string in the main program is what makes it safe.

What happens in Java

Java refuses: > works only on numeric types, so a generic needs T extends Comparable<T> and compareTo.

What happens in C#

C# refuses (CS0019): add where T : IComparable<T> and call CompareTo.

What happens in C

C prints the same. It has no generic functions, so there is no T to leave unbounded: you write one function per type, as the main program does.

What happens in C++

C++ compiles and prints the same. An expression that depends on T, such as >, is checked when the template is used with a type; the page shows the error for a type with no >. A C++20 requires clause or concept states the bound up front, as Rust does.

What Rust sees

Inside largest<T>, T is any type at all, and a type is not promised to have >. Rust checks the body against what the signature promises, so the signature must say it: T: PartialOrd.

Why Rust says no

Rust reads a generic function once, as written, and refuses anything its bounds do not allow. The error comes with a help: that spells out the bound to add. Go, Kotlin, Java and C# refuse the same code and need their own spelling (cmp.Ordered, Comparable<T>, IComparable<T>); TypeScript, Python and C++ accept it, as the outputs show.

The fix

Compiles and runs.

Rust, fixedsrc/main.rsRun ↗
fn largest<T: PartialOrd>(list: &[T]) -> &T {
let mut best = &list[0];
for item in list {
if item > best {
best = item;
}
}
best
}
fn main() {
let numbers = [3, 9, 4];
let words = ["pear", "fig", "apple"];
println!("largest number: {}", largest(&numbers));
println!("largest word: {}", largest(&words));
}
$ cargo run
largest number: 9
largest word: pear

The toolkit

A function’s signature is everything before the body’s { (in a declaration, up to the ;). The standard library’s docs show signatures first, and compiler errors often point at them. Read the name, the parameters, the result, and the bounds last. This page maps your language’s signatures to Rust, decodes five of them, then covers use, pub and ::. Outputs come from Linux on 64-bit ARM with Rust 1.99.1, and each std signature is copied from the 1.99.1 docs.

Your language’s signature, in Rust

Each form is compiled and run, in your language and in Rust. The next section names the Rust pieces.

One program, every piece

Each helper uses one piece of signature syntax. The table names them.

Rustsrc/main.rsRun ↗
use std::error::Error;
use std::fmt::Display;
trait Describe {
fn describe(&self) -> String;
}
struct Square(u32);
impl Describe for Square {
fn describe(&self) -> String {
format!("square of side {}", self.0)
}
}
// `T` is any type that can be printed and cloned
fn label<T: Display + Clone>(item: &T) -> String {
format!("<{}>", item.clone())
}
// two type parameters, with their bounds moved into a `where` clause
fn pair<A, B>(a: &A, b: &B) -> String
where
A: Display,
B: Display,
{
format!("{a}/{b}")
}
// `'a` ties the result to both inputs: it may borrow from either
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if y.len() > x.len() { y } else { x }
}
// an array of exactly 3, and a plain function as a parameter
fn total(values: [i32; 3], tweak: fn(i32) -> i32) -> i32 {
let mut sum = 0;
for v in values {
sum += tweak(v);
}
sum
}
fn double(n: i32) -> i32 {
n * 2
}
// returns some closure; the caller only knows it implements `Fn(i32) -> i32`
fn make_adder(n: i32) -> impl Fn(i32) -> i32 {
move |x| x + n
}
// accepts anything that implements `Describe`, chosen while the program runs
fn show(item: &dyn Describe) -> String {
item.describe()
}
// `Box<dyn Error>` can hold any error type
fn load(text: &str) -> Result<i32, Box<dyn Error>> {
let n = text.trim().parse::<i32>()?;
Ok(n)
}
// `-> !` never returns normally
fn stop(message: &str) -> ! {
panic!("{message}")
}
fn checked(n: i32) -> i32 {
if n < 0 {
stop("negative");
}
n
}
fn main() {
println!("label: {}", label(&"hi"));
println!("pair: {}", pair(&1, &"two"));
println!("longest: {}", longest("ab", "abc"));
println!("total: {}", total([1, 2, 3], double));
println!("adder: {}", make_adder(10)(5));
println!("show: {}", show(&Square(4)));
println!("load: {:?}", load(" 42 ").ok());
println!("load bad: {}", load("x").is_err());
println!("checked: {}", checked(7));
}
$ cargo run
label: <hi>
pair: 1/two
longest: abc
total: 12
adder: 15
show: square of side 4
load: Some(42)
load bad: true
checked: 7
Written It means Learn more
<T> after the name a type parameter, filled in by the call Generics
T: Display + Clone, or where A: Display, a bound: the abilities T must have. A trait is a named ability, and + asks for several. A where clause is the same bounds moved after the result. The body may use what the bounds give, plus what every type has Traits
&T, &mut T a borrow: look at a T, or change it, without taking it. A bare T hands the value over; the caller cannot use it again (a small type such as i32 is copied) Borrowing, Mutable Borrows
self, &self, &mut self, Self a method’s first parameter. Self is the type the impl is for Methods
&[T], Option<&T>, Result<T, E> a slice is any number of items in a row. The result type says whether a call can come back empty or fail Vectors and Slices, Option, Errors
dyn Describe, Box<dyn Error> the method to run is looked up while the program runs. Box owns a heap value (Ownership) Traits
impl Describe in a parameter, shorthand for a bounded type parameter; in a result, “one type of this kind, chosen by the function” Traits
Fn(i32) -> i32, FnMut, FnOnce; fn(i32) -> i32 capital Fn... are bounds for anything callable, such as a closure (an unnamed function, |x| x + 1). Fn needs only shared access to what it captured, FnMut may change it, and FnOnce may use it up, so it is called once. Lowercase fn(i32) -> i32 is a type: a pointer to a plain function, or to a closure that captures nothing Closures, Functions
'a, '_, 'static a lifetime is the span a reference stays valid. 'a ties references together; as a bound, 'static means “holds no borrow that could end early” Returning References
-> ! there is no result to give back Statements and Expressions

Decoding five signatures

largest: this page’s own

fn largest<T: PartialOrd>(list: &[T]) -> &T
Piece Reads as Means
<T: PartialOrd> for any T that can be compared with > PartialOrd allows > even when some pairs, such as a float NaN, cannot be ranked. Ord forbids that, so floats lack it: cmp::max needs Ord, and max(1.5, 0.5) is E0277. largest only compares, so floats are fine
list: &[T] a borrowed slice the caller keeps the list
-> &T a borrow of one item not a copy: returning T out of a slice is E0508 (“consider borrowing here”)

A type with no PartialOrd is refused at the call, and the note points at the bound:

Rustsrc/main.rs✗ won't compile
fn largest<T: PartialOrd>(list: &[T]) -> &T {
let mut best = &list[0];
for item in list {
if item > best {
best = item;
}
}
best
}
struct Point {
x: i32,
y: i32,
}
fn main() {
let points = [Point { x: 1, y: 2 }, Point { x: 3, y: 4 }];
let best = largest(&points);
println!("x: {}, y: {}", best.x, best.y);
}
$ cargo buildWhat is E0277?
error[E0277]: can't compare `Point` with `Point`
  --> src/main.rs:18:24
   |
18 |     let best = largest(&points);
   |                ------- ^^^^^^^ no implementation for `Point < Point` and `Point > Point`
   |                |
   |                required by a bound introduced by this call
   |
   = help: the trait `PartialOrd` is not implemented for `Point`
note: required by a bound in `largest`
  --> src/main.rs:1:15
   |
 1 | fn largest<T: PartialOrd>(list: &[T]) -> &T {
   |               ^^^^^^^^^^ required by this bound in `largest`
help: consider annotating `Point` with `#[derive(PartialOrd)]`
   |
11 + #[derive(PartialOrd)]
12 | struct Point {

A type gets a trait by declaring it. #[derive(...)] writes the impl; PartialOrd needs PartialEq, so derive both:

Rustsrc/main.rsRun ↗
fn largest<T: PartialOrd>(list: &[T]) -> &T {
let mut best = &list[0];
for item in list {
if item > best {
best = item;
}
}
best
}
// a trait is met by declaring it: `derive` writes the `impl` for us
#[derive(PartialEq, PartialOrd)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let points = [Point { x: 1, y: 2 }, Point { x: 3, y: 4 }];
let best = largest(&points);
println!("x: {}, y: {}", best.x, best.y);
}
$ cargo run
x: 3, y: 4

Option::map: a closure bound

Option::map changes the value inside an Option:

pub fn map<U, F>(self, f: F) -> Option<U>
where
F: FnOnce(T) -> U,
Piece Reads as Means
map<U, F> two new placeholders U is what the closure gives back. F is the closure’s own type: every closure has its own type, so a parameter that takes one is a type parameter with a bound. T comes from the Option<T> the method belongs to
self the option itself the method takes the Option (an Option<i32> is copied, so it stays usable)
f: F, F: FnOnce(T) -> U a callable taking the T, giving a U called at most once, so it may use up what it captured
-> Option<U> maybe a U None stays None
Rustsrc/main.rsRun ↗
fn main() {
// T = &str (what the Option holds), U = usize (what the closure returns)
let length = Some("hello").map(|s| s.len());
let nothing = None::<&str>.map(|s| s.len());
println!("length: {length:?}");
println!("nothing: {nothing:?}");
}
$ cargo run
length: Some(5)
nothing: None

HashMap::entry: a mutable borrow

HashMap::entry finds the slot for a key, whether or not it has a value yet (Maps):

pub fn entry(&mut self, key: K) -> Entry<'_, K, V, A>

K and V, the key and value types, are not in the method’s own <>. They come from the line the docs print above it, impl<K, V, S, A> HashMap<K, V, S, A> where K: Eq + Hash, S: BuildHasher, A: Allocator, which declares the generics for every method in that block. Its K: Eq + Hash is why keys must be hashable. Ignore S, the hasher, and A, the allocator: on stable Rust A is always Global (a custom one is unstable, E0658).

Piece Reads as Means
&mut self borrowed to change the map must be reachable mutably (a let mut variable or a &mut reference), and nothing else may use it while the result lives
key: K the key, by value moved in. A HashMap<String, _> needs entry(word.to_string()), not entry(word) with a &str
Entry<'_, K, V, A> a handle for one key’s slot, filled or empty '_ is a lifetime the compiler fills in: the Entry cannot outlive the borrow of the map
Rustsrc/main.rsRun ↗
use std::collections::HashMap;
fn main() {
let mut counts: HashMap<String, u32> = HashMap::new();
for word in ["red", "blue", "red"] {
// `entry` borrows the map mutably and takes the key by value
*counts.entry(word.to_string()).or_insert(0) += 1;
}
println!("red: {}", counts["red"]);
println!("blue: {}", counts["blue"]);
println!("keys: {}", counts.len());
}
$ cargo run
red: 2
blue: 1
keys: 2

or_insert(0) fills an empty slot and hands back a &mut to the count.

thread::spawn: three bounds at once

thread::spawn starts a thread (Threads):

pub fn spawn<F, T>(f: F) -> JoinHandle<T>
where
F: FnOnce() -> T + Send + 'static,
T: Send + 'static,
Piece Reads as Means
F: FnOnce() -> T + Send + 'static F is all three callable once with no arguments, giving a T; Send means it may be moved to another thread; 'static means it holds no borrow of a local variable that could end first
-> JoinHandle<T> a handle .join() waits for the thread and gives Ok(T), or Err if the thread panicked. T: Send + 'static lets the result cross back

A closure that only uses a local borrows it, and the borrow could end before the thread does. 'static refuses that; the help: shows the fix, move:

Rustsrc/main.rs✗ won't compile
use std::thread;
fn main() {
let name = String::from("Ferris");
let handle = thread::spawn(|| {
println!("hello, {name}");
});
handle.join().unwrap();
}
$ cargo buildWhat is E0373?
error[E0373]: closure may outlive the current function, but it borrows `name`, which is owned by the current function
 --> src/main.rs:5:32
  |
5 |     let handle = thread::spawn(|| {
  |                                ^^ may outlive borrowed value `name`
6 |         println!("hello, {name}");
  |                           ---- `name` is borrowed here
  |
note: function requires argument type to outlive `'static`
 --> src/main.rs:5:18
  |
5 |       let handle = thread::spawn(|| {
  |  __________________^
6 | |         println!("hello, {name}");
7 | |     });
  | |______^
help: to force the closure to take ownership of `name` (and any other referenced variables), use the `move` keyword
  |
5 |     let handle = thread::spawn(move || {
  |                                ++++

move makes the closure own the variables it uses, so nothing is borrowed:

Rustsrc/main.rsRun ↗
use std::thread;
fn main() {
let name = String::from("Ferris");
// `move` makes the closure own `name` instead of borrowing it
let handle = thread::spawn(move || name.len());
println!("joined: {:?}", handle.join());
}
$ cargo run
joined: Ok(6)

fs::read_to_string: a conversion bound, and a shortened Result

fs::read_to_string reads a whole file:

pub fn read_to_string<P: AsRef<Path>>(path: P) -> Result<String>

P: AsRef<Path> means “anything viewable as a file path”: a &str, a String or a Path. Result<String> has one parameter because this Result is io::Result, an alias for Result<String, io::Error>. Box<dyn Error> holds any error, and ? converts into it:

Rustsrc/main.rsRun ↗
use std::error::Error;
use std::fs;
fn read(path: &str) -> Result<String, Box<dyn Error>> {
let text = fs::read_to_string(path)?; // an io::Error becomes a Box<dyn Error>
Ok(text)
}
fn main() {
match read("/no/such/file") {
Ok(text) => println!("read {} bytes", text.len()),
Err(e) => println!("error: {e}"),
}
}
$ cargo run
error: No such file or directory (os error 2)

Two more at a glance

Signature What it already tells you
pub fn exit(code: i32) -> ! never comes back
fn map<B, F>(self, f: F) -> Map<Self, F> where Self: Sized, F: FnMut(Self::Item) -> B on Iterator Self::Item is what the iterator yields; skip Self: Sized; the result is a new iterator that does nothing until used (Iterators)

Paths: use, pub and mod

A path names an item by walking through modules with ::. A module (mod) is a named group of items, and a crate is one unit of compilation: your program is one, and std is another. This program uses each path form:

Rustsrc/main.rsRun ↗
use std::collections::{BTreeMap, HashSet};
mod shop {
pub const TAX_PERCENT: u32 = 10;
pub mod prices {
// a path starting at `crate::` begins at the top of the program
pub fn with_tax(cents: u32) -> u32 {
cents + cents * crate::shop::TAX_PERCENT / 100
}
// `super::` starts one module up
pub fn doubled_with_tax(cents: u32) -> u32 {
with_tax(super::double(cents))
}
}
fn double(n: u32) -> u32 {
n * 2
}
// visible in this program, but not meant for outside code
pub(crate) fn describe() -> String {
// `self::` starts in the current module
format!("tax is {}%", self::TAX_PERCENT)
}
}
use shop::prices::with_tax as taxed;
fn main() {
let mut ages = BTreeMap::new();
ages.insert("Ada", 36);
let seen: HashSet<i32> = HashSet::new();
println!("taxed: {}", taxed(1000));
println!("doubled: {}", shop::prices::doubled_with_tax(1000));
println!("describe: {}", shop::describe());
println!("sizes: {} {}", ages.len(), seen.len());
println!("method vs path: {} {}", "abc".len(), str::len("abc"));
}
$ cargo run
taxed: 1100
doubled: 2200
describe: tax is 10%
sizes: 1 0
method vs path: 3 3
Written Means
use std::collections::HashMap; bring the name in, so HashMap can be written short
use std::collections::{BTreeMap, HashSet};, use std::cmp::*; several names; every public name in a module
use shop::prices::with_tax as taxed; bring it in under another name
crate::shop::TAX_PERCENT, self::TAX_PERCENT, super::double start at the top of this crate, at this module, or at the one above it
pub, pub(crate), nothing visible outside the module; visible in this crate only; private (trait items and enum variants follow their trait or enum)
str::len("abc") against "abc".len() :: walks to a function by name; . calls a method on a value you hold

A private item is visible in its own module and the modules inside it: double has no pub, and prices still calls it through super::. A name used without its use is E0433 when it starts a path (HashMap::new()) and E0425 when it stands alone (a call like max(3, 9)). The help: supplies the line either way:

Rustsrc/main.rs✗ won't compile
fn main() {
let mut scores = HashMap::new();
scores.insert("Ada", 3);
println!("scores: {}", scores.len());
}
$ cargo buildWhat is E0433?
error[E0433]: cannot find type `HashMap` in this scope
 --> src/main.rs:2:22
  |
2 |     let mut scores = HashMap::new();
  |                      ^^^^^^^ use of undeclared type `HashMap`
  |
help: consider importing this struct
  |
1 + use std::collections::HashMap;
Rustsrc/main.rs✗ won't compile
fn main() {
println!("max: {}", max(3, 9));
}
$ cargo buildWhat is E0425?
error[E0425]: cannot find function `max` in this scope
 --> src/main.rs:2:25
  |
2 |     println!("max: {}", max(3, 9));
  |                         ^^^ not found in this scope
  |
help: consider importing this function
  |
1 + use std::cmp::max;

And a private function reached from outside is E0603:

Rustsrc/main.rs✗ won't compile
mod bank {
fn secret_rate() -> u32 {
5
}
pub fn rate() -> u32 {
secret_rate()
}
}
fn main() {
println!("rate: {}", bank::rate());
println!("secret: {}", bank::secret_rate());
}
$ cargo buildWhat is E0603?
error[E0603]: function `secret_rate` is private
  --> src/main.rs:13:34
   |
13 |     println!("secret: {}", bank::secret_rate());
   |                                  ^^^^^^^^^^^ private function
   |
note: the function `secret_rate` is defined here
  --> src/main.rs:2:5
   |
 2 |     fn secret_rate() -> u32 {
   |     ^^^^^^^^^^^^^^^^^^^^^^^

Going deeper

The bound is checked where you write it

Rust checks a generic function’s body once, against its bounds. That is why the gotcha fails at the > inside largest, and why a bad call fails at the call, as the Point error above did.

What the first parameter promises

self, &self and &mut self tell you what a call may do to the value. finish(self) takes the counter and does not give it back, so a second use is an error (Counter is not Copy). The note points at the self:

Rustsrc/main.rs✗ won't compile
struct Counter {
total: u32,
}
impl Counter {
fn finish(self) -> u32 {
self.total
}
}
fn main() {
let c = Counter { total: 7 };
println!("finish: {}", c.finish());
println!("again: {}", c.finish());
}
$ cargo buildWhat is E0382?
error[E0382]: use of moved value: `c`
  --> src/main.rs:14:27
   |
12 |     let c = Counter { total: 7 };
   |         - move occurs because `c` has type `Counter`, which does not implement the `Copy` trait
13 |     println!("finish: {}", c.finish());
   |                              -------- `c` moved due to this method call
14 |     println!("again: {}", c.finish());
   |                           ^ value used here after move
   |
note: `Counter::finish` takes ownership of the receiver `self`, which moves `c`
  --> src/main.rs:6:15
   |
 6 |     fn finish(self) -> u32 {
   |               ^^^^

&self says “shared access”, which for ordinary values means read-only; Cell and Mutex can change behind a & (Smart pointers, Shared state).

'a: why it appears, and when it does not

A reference in a result must point at something, and the compiler must know what. With one reference in the parameters, or a &self, it works that out, so you write no 'a, as in fn first_word(text: &str) -> &str. With two references and neither of them &self, it cannot guess:

Rustsrc/main.rs✗ won't compile
fn longest(x: &str, y: &str) -> &str {
if y.len() > x.len() { y } else { x }
}
fn main() {
println!("longest: {}", longest("ab", "abc"));
}
$ cargo buildWhat is E0106?
error[E0106]: missing lifetime specifier
 --> src/main.rs:1:33
  |
1 | fn longest(x: &str, y: &str) -> &str {
  |               ----     ----     ^ expected named lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y`
help: consider introducing a named lifetime parameter
  |
1 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
  |           ++++     ++          ++          ++

The help: is the fix. Annotations “don’t change how long any of the references live”, says the Rust Book; read 'a as a label that ties the output to the inputs carrying it (Lifetimes comes later).

Practice

The first asks you to read rather than write.

Read this: what does each call give?

Four short signatures (&mut, impl FnMut, a PartialOrd bound, an Option result) and the calls that use them. Say what each call gives; the checks run the real code and name the item you misread, not the right answer. Two methods you have not met: list.first() is the first item as an Option<&i32> (None for an empty list), and .copied() turns that into an Option<i32>.

Starterexercises/read-calls.rs
// Read the signatures, then say what each call gives. Replace each `todo!()` and run
// until the checks print `all checks passed`.
//
// Two methods you have not met: `list.first()` is the first item of a slice, as an
// `Option<&i32>` (`None` when the slice is empty), and `.copied()` turns an
// `Option<&i32>` into an `Option<i32>` by copying the number out of the borrow.
fn bump(x: &mut i32) {
*x += 1;
}
fn twice(mut f: impl FnMut() -> i32) -> i32 {
f() + f()
}
fn pick<T: PartialOrd>(a: T, b: T) -> T {
if a > b { a } else { b }
}
fn head(list: &[i32]) -> Option<&i32> {
list.first()
}
fn calls() -> (i32, i32, &'static str, Option<i32>, Option<i32>) {
let mut n = 5;
bump(&mut n);
bump(&mut n);
let mut c = 0;
let t = twice(|| {
c += 1;
c
});
let (full, empty) = ([8, 9], []);
(
n,
t,
pick("pear", "zebra"),
head(&full).copied(),
head(&empty).copied(),
)
}
/// Hint: `bump` takes `&mut i32`: does the caller's `n` change?
fn n_after_bumps() -> i32 {
todo!("`bump` takes `&mut i32`: does the caller's `n` change?")
}
/// Hint: `impl FnMut` may keep state between calls: what does the closure return each time?
fn twice_gives() -> i32 {
todo!("`impl FnMut` may keep state between calls: what does the closure return each time?")
}
/// Hint: `T: PartialOrd` lets `pick` use `>`: which of the two words is larger?
fn pick_gives() -> &'static str {
todo!("`T: PartialOrd` lets `pick` use `>`: which of the two words is larger?")
}
/// Hint: the result is an `Option` of a borrow: which item is first?
fn head_of_two_gives() -> Option<i32> {
todo!("the result is an `Option` of a borrow: which item is first?")
}
/// Hint: the list is empty
fn head_of_none_gives() -> Option<i32> {
todo!("the list is empty")
}
fn check<T: PartialEq + std::fmt::Debug>(call: &str, said: T, truth: T) {
assert!(said == truth, "misread `{call}`: you said {said:?}");
println!("ok: {call} = {said:?}");
}
fn main() {
let (n, t, p, first, none) = calls();
check("n after the two bumps", n_after_bumps(), n);
check("twice(..) for the counting closure", twice_gives(), t);
check("pick(\"pear\", \"zebra\")", pick_gives(), p);
check("head of [8, 9]", head_of_two_gives(), first);
check("head of an empty list", head_of_none_gives(), none);
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
One solutionexercises/read-calls.solution.rsRun ↗
// Read the signatures, then say what each call gives. Replace each `todo!()` and run
// until the checks print `all checks passed`.
//
// Two methods you have not met: `list.first()` is the first item of a slice, as an
// `Option<&i32>` (`None` when the slice is empty), and `.copied()` turns an
// `Option<&i32>` into an `Option<i32>` by copying the number out of the borrow.
fn bump(x: &mut i32) {
*x += 1;
}
fn twice(mut f: impl FnMut() -> i32) -> i32 {
f() + f()
}
fn pick<T: PartialOrd>(a: T, b: T) -> T {
if a > b { a } else { b }
}
fn head(list: &[i32]) -> Option<&i32> {
list.first()
}
fn calls() -> (i32, i32, &'static str, Option<i32>, Option<i32>) {
let mut n = 5;
bump(&mut n);
bump(&mut n);
let mut c = 0;
let t = twice(|| {
c += 1;
c
});
let (full, empty) = ([8, 9], []);
(
n,
t,
pick("pear", "zebra"),
head(&full).copied(),
head(&empty).copied(),
)
}
/// Hint: `bump` takes `&mut i32`: does the caller's `n` change?
fn n_after_bumps() -> i32 {
7
}
/// Hint: `impl FnMut` may keep state between calls: what does the closure return each time?
fn twice_gives() -> i32 {
3
}
/// Hint: `T: PartialOrd` lets `pick` use `>`: which of the two words is larger?
fn pick_gives() -> &'static str {
"zebra"
}
/// Hint: the result is an `Option` of a borrow: which item is first?
fn head_of_two_gives() -> Option<i32> {
Some(8)
}
/// Hint: the list is empty
fn head_of_none_gives() -> Option<i32> {
None
}
fn check<T: PartialEq + std::fmt::Debug>(call: &str, said: T, truth: T) {
assert!(said == truth, "misread `{call}`: you said {said:?}");
println!("ok: {call} = {said:?}");
}
fn main() {
let (n, t, p, first, none) = calls();
check("n after the two bumps", n_after_bumps(), n);
check("twice(..) for the counting closure", twice_gives(), t);
check("pick(\"pear\", \"zebra\")", pick_gives(), p);
check("head of [8, 9]", head_of_two_gives(), first);
check("head of an empty list", head_of_none_gives(), none);
println!("all checks passed");
}
$ cargo run
ok: n after the two bumps = 7
ok: twice(..) for the counting closure = 3
ok: pick("pear", "zebra") = "zebra"
ok: head of [8, 9] = Some(8)
ok: head of an empty list = None
all checks passed

Largest, or nothing

Fill in largest: the biggest item, or None for an empty list (return None early). The compiler’s help: names the bound. The tempting list.iter().max() needs Ord, and the float check shows why it fails.

Starterexercises/largest.rs
// The signature is almost right. Fill in the body, and add what `T` needs.
fn largest<T>(list: &[T]) -> Option<&T> {
todo!(
"return None early if the list is empty; otherwise keep a `best` reference and compare each item; the compiler will say which bound T needs"
)
}
fn main() {
assert_eq!(largest(&[3, 9, 4]), Some(&9));
assert_eq!(largest(&[1.5, -2.0]), Some(&1.5));
assert_eq!(largest(&["pear", "fig"]), Some(&"pear"));
let empty: [i32; 0] = [];
assert_eq!(largest(&empty), None);
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
One solutionexercises/largest.solution.rsRun ↗
// The signature is almost right. Fill in the body, and add what `T` needs.
fn largest<T: PartialOrd>(list: &[T]) -> Option<&T> {
if list.is_empty() {
return None;
}
let mut best = &list[0];
for item in list {
if item > best {
best = item;
}
}
Some(best)
}
fn main() {
assert_eq!(largest(&[3, 9, 4]), Some(&9));
assert_eq!(largest(&[1.5, -2.0]), Some(&1.5));
assert_eq!(largest(&["pear", "fig"]), Some(&"pear"));
let empty: [i32; 0] = [];
assert_eq!(largest(&empty), None);
println!("all checks passed");
}
$ cargo run
all checks passed

Call it n times

Call the closure f exactly n times. A closure that changes state must itself be mut to be called, so the parameter list needs one more word than the starter has; the compiler will say which.

Starterexercises/repeat.rs
// Call `f` exactly `n` times. The bound says `f` may change what it captured.
fn repeat(n: usize, f: impl FnMut()) {
todo!(
"loop `n` times and call `f()`; the compiler will ask for one more word in the parameter list"
)
}
fn main() {
let mut count = 0;
repeat(3, || count += 1);
assert_eq!(count, 3);
let mut log = Vec::new();
repeat(2, || log.push("tick"));
assert_eq!(log, ["tick", "tick"]);
repeat(0, || panic!("must not be called"));
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
One solutionexercises/repeat.solution.rsRun ↗
// Call `f` exactly `n` times. The bound says `f` may change what it captured.
fn repeat(n: usize, mut f: impl FnMut()) {
for _ in 0..n {
f();
}
}
fn main() {
let mut count = 0;
repeat(3, || count += 1);
assert_eq!(count, 3);
let mut log = Vec::new();
repeat(2, || log.push("tick"));
assert_eq!(log, ["tick", "tick"]);
repeat(0, || panic!("must not be called"));
println!("all checks passed");
}
$ cargo run
all checks passed

Count with entry

Count how often each word appears. The key is a String, passed to entry by value. The checks include an empty list.

Starterexercises/count-words.rs
use std::collections::HashMap;
// `entry(&mut self, key: K)` takes the key by value, and `K` is `String` here.
fn count_words(words: &[&str]) -> HashMap<String, usize> {
todo!("make an empty map, then for each word: `*map.entry(key).or_insert(0) += 1`")
}
fn main() {
let counts = count_words(&["red", "blue", "red"]);
assert_eq!(counts["red"], 2);
assert_eq!(counts["blue"], 1);
assert_eq!(counts.get("green"), None);
assert_eq!(counts.len(), 2);
assert!(count_words(&[]).is_empty());
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
One solutionexercises/count-words.solution.rsRun ↗
use std::collections::HashMap;
// `entry(&mut self, key: K)` takes the key by value, and `K` is `String` here.
fn count_words(words: &[&str]) -> HashMap<String, usize> {
let mut map = HashMap::new();
for word in words {
*map.entry(word.to_string()).or_insert(0) += 1;
}
map
}
fn main() {
let counts = count_words(&["red", "blue", "red"]);
assert_eq!(counts["red"], 2);
assert_eq!(counts["blue"], 1);
assert_eq!(counts.get("green"), None);
assert_eq!(counts.len(), 2);
assert!(count_words(&[]).is_empty());
println!("all checks passed");
}
$ cargo run
all checks passed

Sum in a thread

Sum a Vec<i32> on another thread. The closure must own the vector (move), and join() hands back a Result. The checks include an empty vector.

Starterexercises/sum-in-thread.rs
use std::thread;
// `spawn` needs `F: FnOnce() -> T + Send + 'static`: the closure may not borrow `numbers`.
fn sum_in_thread(numbers: Vec<i32>) -> i32 {
todo!("spawn a `move` closure that sums `numbers`, then `join()` it; `join` gives a Result")
}
fn main() {
assert_eq!(sum_in_thread(vec![1, 2, 3]), 6);
assert_eq!(sum_in_thread(vec![]), 0);
assert_eq!(sum_in_thread((1..=100).collect()), 5050);
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
One solutionexercises/sum-in-thread.solution.rsRun ↗
use std::thread;
// `spawn` needs `F: FnOnce() -> T + Send + 'static`: the closure may not borrow `numbers`.
fn sum_in_thread(numbers: Vec<i32>) -> i32 {
let handle = thread::spawn(move || {
let mut total = 0;
for n in numbers {
total += n;
}
total
});
handle.join().unwrap()
}
fn main() {
assert_eq!(sum_in_thread(vec![1, 2, 3]), 6);
assert_eq!(sum_in_thread(vec![]), 0);
assert_eq!(sum_in_thread((1..=100).collect()), 5050);
println!("all checks passed");
}
$ cargo run
all checks passed

Read more