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
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));}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))}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)}")}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))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));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)); }}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)}");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;}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";}largest number: 9 largest word: pear
What's different
- A generic function lists its type parameters in
<>after its name.T: PartialOrdis a bound: it says aTmust be able to be compared with>and<, and+joins several bounds. &Tis a borrowed look at aT,&mut Ta borrow that may change it, and a bareThands 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", andResult<T, E>means "aT, or an errorE", 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 asstd::cmp::max, andusebrings a name in so you can write it short.
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.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: 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: 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))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));largest number: 9 largest word: pear
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 errorC# 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;}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";}largest number: 9 largest word: pear
What does the compiler say? Guess first, then open.
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 { | ++++++++++++++++++++++
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.
Kotlin refuses: an unbounded T has no >. Write <T : Comparable<T>>.
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.
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.
Java refuses: > works only on numeric types, so a generic needs T extends Comparable<T> and compareTo.
C# refuses (CS0019): add where T : IComparable<T> and call CompareTo.
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.
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.
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.
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.
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));}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.
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 clonedfn label<T: Display + Clone>(item: &T) -> String { format!("<{}>", item.clone())}
// two type parameters, with their bounds moved into a `where` clausefn pair<A, B>(a: &A, b: &B) -> Stringwhere A: Display, B: Display,{ format!("{a}/{b}")}
// `'a` ties the result to both inputs: it may borrow from eitherfn 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 parameterfn 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 runsfn show(item: &dyn Describe) -> String { item.describe()}
// `Box<dyn Error>` can hold any error typefn load(text: &str) -> Result<i32, Box<dyn Error>> { let n = text.trim().parse::<i32>()?; Ok(n)}
// `-> !` never returns normallyfn 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));}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:
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);}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:
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);}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 |
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:?}");}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 |
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());}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:
use std::thread;
fn main() { let name = String::from("Ferris"); let handle = thread::spawn(|| { println!("hello, {name}"); }); handle.join().unwrap();}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:
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());}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:
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}"), }}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:
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"));}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:
fn main() { let mut scores = HashMap::new(); scores.insert("Ada", 3); println!("scores: {}", scores.len());}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;
fn main() { println!("max: {}", max(3, 9));}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:
mod bank { fn secret_rate() -> u32 { 5 }
pub fn rate() -> u32 { secret_rate() }}
fn main() { println!("rate: {}", bank::rate()); println!("secret: {}", bank::secret_rate());}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:
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());}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:
fn longest(x: &str, y: &str) -> &str { if y.len() > x.len() { y } else { x }}
fn main() { println!("longest: {}", longest("ab", "abc"));}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>.
// 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 emptyfn 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
// 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 emptyfn 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");}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 passedLargest, 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.
// 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
// 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");}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.
// 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
// 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");}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.
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
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");}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.
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
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");}all checks passed
Read more
- The Rust Book, Generic Data Types, Traits (bounds,
where,impl Trait), Lifetimes, Paths anduse. - The Rust Reference, Generic parameters, Trait and lifetime bounds, Impl trait type, Lifetime elision and Visibility and privacy.
- The standard library,
Fn,FnMutandFnOnce. Rust by Example, Bounds, Where clauses and Visibility.
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.