Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Types

FoundationValues · inference · signaturesDraft

Every value in Rust has one concrete type. The compiler can often infer that type, but inference does not make the program dynamically typed. It means the compiler found the single type that satisfies every use of the value.

fn double(number: i32) -> i32 {
    number * 2
}

fn main() {
    let score = 21;
    let result = double(score);
    println!("{result}");
}

The annotation on double gives the compiler enough context to infer that both score and result are i32. The function signature is also a contract for a reader:

double: i32 → i32

It accepts one signed 32-bit integer and produces another. Passing text or a floating-point value violates that contract before the program can run.

Start with values

Rust’s common built-in values include:

#![allow(unused)]
fn main() {
let count: u32 = 3;
let temperature: f64 = 21.5;
let enabled: bool = true;
let initial: char = 'R';
}

The annotation after each binding name states its type explicitly. Usually you can omit an annotation when the initializer or later use makes the type clear:

#![allow(unused)]
fn main() {
let enabled = true;      // bool
let greeting = "hello"; // &str
}

Annotations are most useful when they communicate intent or resolve a real ambiguity. Adding them everywhere can obscure the facts that matter.

Combine types

Tuples group a fixed number of values that may have different types:

#![allow(unused)]
fn main() {
let user: (&str, u32, bool) = ("Ferris", 8, true);
let (name, level, active) = user;
}

Arrays group a fixed number of values of the same type:

#![allow(unused)]
fn main() {
let scores: [i32; 3] = [10, 20, 30];
}

These type spellings already reveal useful constraints. (&str, u32, bool) has three positions with distinct meanings. [i32; 3] has exactly three elements, all i32. Later chapters replace positional tuples with structs when the fields deserve names and replace fixed arrays with vectors when the length must change.

A mismatch is information

fn main() {
    let mut answer = 42;
    answer = "forty-two";
}

mut allows the value stored in answer to change. It does not allow the binding’s type to change. Once answer is inferred as an integer, every later assignment must still produce that integer type.

When Rust reports mismatched types, read the diagnostic as two facts:

expected: the type required by this position
   found: the type the expression actually produced

The useful debugging question is not merely “How do I silence this error?” It is “Which side expresses the contract I intended?” Sometimes the expression is wrong; sometimes the function signature is too restrictive.

Types carry different kinds of promises

As the book progresses, type syntax will answer increasingly rich questions:

String       owns growable text
&str         borrows a view of text
Option<T>    contains either a T or no value
Result<T, E> contains either a T or an error E
Vec<T>       owns a growable sequence of T values
&[T]         borrows a view of a sequence of T values

Rust code becomes much easier to read when these are treated as descriptions of program states rather than punctuation to memorize.