Rust

A quick language tour
Last update: 2026-02

Official websites and logos

Official logo

rust-lang.org
The Rust book

Unofficial mascot

Ferris the crab
The cargo book
Crates logo

Dependency registry

crates.io
docs.rs

Why Rust

Rust type system and memory model are designed to enforce correctness. Emphasis is placed on the compiler to provide clear explanations for errors along with suggestions on how to fix them.
  • Static dispatch by default, but dynamic dispatch is still possible.
  • No garbage collector, instead it has an ownership model (borrow checker) that provides memory and thread safety.
  • Modern functional-style constructs with zero runtime cost.
  • Errors are handled with dedicated types, with special syntax to automatically convert and propagate them up the stack.
  • Pattern matching makes sure you handle all cases and can only access variants when they are valid.
  • No classes with inheritance, only structs, but with a powerful trait system and generics with constraints.

Tooling

Toolchain installer and version manager

rustup

Build system and package manager

cargo
Includes a test runner

Compiler

rustc (cargo check, cargo build)

Formatter

rustfmt (cargo fmt)

Linter

clippy (cargo clippy)

Documentation generator

rustdoc (cargo doc)

Undefined Behavior Detection

Miri

Language server

Rust Analyzer

rustup

The rust toolchain manager.
Download rustup from rustup.rs
The rustup book
Forge

Examples

Update everything:rustup update
Install wasm target (minimum tools):
rustup toolchain install stable --profile minimal --target wasm32-unknown-unknown
Set default to the nightly channel: rustup default nightly
Show installed components and defaults: rustup show

Channels

  • stable
  • beta
  • nightly
    (for experimental features)

Target Triple

  • {arch}-{vendor}-{sys}-{env}
  • arch(x86_64, arm)
  • vendor(pc, apple)
  • sys(linux, macos, windows)
  • env(gnu, msvc)

Profiles

  • minimal
    Only what is needed to build
  • default
  • For additional components, use:
    rustup component add {component}

cargo

Rust build system and package manager.
The cargo book
clippy doc
crates.io dependency registry

Project management

Creates a library project: cargo init --lib
Prints a tree of dependencies: cargo tree
Adds "tokio" 1.x as a dependency with the "full" feature:
cargo add tokio@1 --features full
Updates Cargo.lock with the latest versions of the dependencies that still satisfy the constraints: cargo update

Compiler commands

Check for compile errors and warnings:
cargo check --all-features --all-targets
Apply fixes suggested by the compiler to fix warnings:
cargo fix --all-features --all-targets --broken-code
Creates a release binary with all the features enabled:
cargo build --release --features full

Clippy

Run the linter: cargo clippy
Apply suggested fixes: rustup clippy --fix

Tests

Run all the tests:
cargo test --all-features --no-fail-fast
Run a single "example" test with stdout output:
cargo test example -- --no-capture

Run commands

Build and run the "demo" example in release mode:
cargo run --release --example demo

Project structure

  • .cargo
    • config.toml
  • examples
    • example1.rs
      • main.rs
      • utils.rs
    • example2.rs
  • src
    • bin
      • bin1
        • utils.rs
        • main.rs
      • bin2.rs
    • mod1
      • mod.rs
      • utils.rs
    • lib.rs
    • main.rs
    • mod2.rs
  • target
  • tests
    • integration-test1.rs
      • main.rs
      • utils.rs
    • integration-test2.rs
  • build.rs
  • Cargo.lock
  • Cargo.toml
  • rust-toolchain.toml
The only required files are the Cargo.toml package manifest and src/main.rs with a main function for a binary project or src/lib.rs for a library project.
A crate is a unit of compilation: either a binary or a library. A single package (described by its Cargo.toml manifest) can have many binary crates, but only one library crate (or none).
Cargo expects all entries under the src/bin directory to be separate binaries (with a main function).
Cargo creates a Cargo.lock lock file when it resolves dependencies for the first time, and updates it every time you add, remove, or update dependencies in the manifest.
build.rs is an optional build script that is compiled and run before the crates are compiled.
A rust-toolchain.toml file can be used to override rustup defaults and specify a specific toolchain to use for the package.
Project-specific Cargo configuration options can be specified in a .cargo/config.toml file.
Unit tests usually reside in a test submodule in the same file as the code they test. This grants them access to elements visible only within that crate. Integration tests go in a dedicated tests directory of the package root.
Examples also have a dedicated examples directory in the package root.
A module entry point can be a single .rs file named after the module, or a mod.rs file within a directory named after the module. Submodules can live in the directory regardless.
A binary entry point can also be a single .rs file or a directory with a main.rs entry point inside it. This also applies to tests and examples.
The default library entry point of the package: lib.rs, cannot be replaced with a directory alternative without editing the Cargo.toml manifest to specify its path explicitly. The same goes for the default binary entry point: main.rs.

Cargo.toml

The package manifest.
[package]
name = "my-project"
version = "0.1.0"
edition = "2024"
[dependencies]
tracing = { optional = true, version = "0.1" }
serde = { version = "1", features = [ "derive" ] }
serde_json = "1"
[dependencies.tokio]
version = "1"
default-features = false
features = [ "rt", "fs" ]
[dev-dependencies]
tracing-subscriber = "0.3"
[features]
default = [ "std" ]
std = []
tracing = [ "dep:tracing" ]
log = [ "tracing?/log-always" ]
[[example]]
name = "demo"
required-features = [ "tracing" ]
[profile.release]
codegen-units = 1
[lints.rust]
unsafe_code = "forbid"
[lints.clippy]
collapsible_if = "allow"
The only required fields are in the [package] section: name, version and edition.
The package name should use kebab-case. The crate name defaults to the same name converted to snake_case.
Rust editions are used to introduce changes to the language without braking compatibility. They can change the compiler behavior and add new syntax and keywords.
cargo fix --edition adapts the code to a new edition.
Crates using different editions can still depend on each other.
Packages can define optional, named features. They specify which ones are enabled by default.
Features can enable other features, optional dependencies, and even dependency features.
For the project dependencies, you can keep the default features, add some, or disable the defaults and select features explicitly.
By default, dependencies are fetched from the crates.io registry, but you can also specify another registry, a local path, or a git repository with an optional branch, tag, or revision.
Credentials for alternate repositories can be defined globally or in the project .cargo/config.toml file.
You can rename dependencies to avoid conflicts, specify different dependencies based on the platform, and override inherited dependency versions.
dev-dependencies are only available for the tests, and the examples.
build-dependencies are only available for the build script.
Binaries and examples can declare that they require some features to be enabled.
You can change the default profile options and even create new profiles. By default, the dev profile (used by cargo build unless another profile is explicitly specified) keeps debugging information and disables optimizations to make the build faster. The release profile is optimized for size and speed but takes a lot longer to build.
You can customize the lints levels and priority.

Comments

Line comments

// This is a line comment
let first = true; // Another line comment

Block comments

/*
   Block comments
   can span multiple lines,
   and can be /* nested */.
*/
let n = 2.5 /* + 1.2 */;

Doc comments

They are used by rustdoc to generate the documentation.
Markdown formatting can be used, including code blocks for examples.
Those examples can use dev dependencies, and are treated as tests.
/// Struct description
/// # Example
/// ```
/// let example = Example;
/// ```
struct Example;

Variables

Variable declaration and initialization

let variable_name: bool = true;
  • The variable is immutable by default.
  • Variables are scoped, they are only valid inside the current block.
  • The type can be omitted if it can be inferred.
  • You can skip the initialization as long as you do it before the first use.
  • The convention is to use snake_case for the variable name.
  • The name _ is reserved for variables (or bindings) that won't be used.
  • Names starting with _ indicate that you intentionally might not use a value marked as #[must_use].
let one_megabyte = 1_048_576_u32;
  • You can disambiguate number types with a suffix on the value.
  • You can use _ as an optional separator.

Mutable variable

let mut variable_name: bool = true;
variable_name = false;

Shadowing

let mut data = read_data(&mut vec![]);
let mut data = read_data(&mut data);
let data = data; // drop the mut
  • You can shadow a variable by reusing its name, rendering the previous one unreachable.

Constants

const CONSTANT_NAME: bool = true;
  • The value must be known at compile time, as the compiler simply inlines it.
  • Its scope is the same as a regular variable.
  • The convention is to use SCREAMING_SNAKE_CASE for the constant name.
  • You can't omit the type.

Static globals

static GLOBAL_NAME: bool = true;
  • The value must be known at compile time.
  • Its scope is the same as a regular variable, even though it lives for the entire program duration.
  • The convention is to use SCREAMING_SNAKE_CASE for the global name.
  • You can't omit the type.
  • If it's mutable, you can only access it from an unsafe block, as it is your responsibility to ensure thread safety.

Primitive types

Signed integers

i8, i16, i32, i64, i128,
isize(platform specific)

Unsigned integers

u8, u16, u32, u64, u128,
usize(platform specific)

Floating points

f32, f64

Boolean

bool

Character

char (4-byte unicode value)

Unicode sequences

str (valid utf-8 byte sequences)

Unit

() (empty tuple)

Never

! (something that never happens or never finishes)

References (borrows)

& (borrow)
&mut (mutable borrow)
let mut a = 1;
let b = &a;
assert_eq!(*b, 1);
let c = &mut a;
*c = 2;
assert_eq!(a, 2);

Raw pointers

*const (const* in C)
*mut (* in C)
You cannot dereference them outside unsafe blocks because it is your responsibility to ensure they still point to valid memory.
let x = 1.2;
let x_ptr: *const f64 = &x as *const f64;
assert_eq!(unsafe { *x_ptr }, 1.2);
let mut y = 0.5;
let y_ptr: *mut f64 = &mut y;
unsafe { *y_ptr = 1.5; }
assert_eq!(y, 1.5);

String literals

Double-quoted

You can use backslash-escapes for double quotes, backslashes, hex values and unicode characters.
They can span multiple lines with the use of a trailing backslash.
The next line leading whitespace is automatically trimmed.
let text: &str = "text";
let text: &str = "\
Some inner 'single quoted' text and\nEscape\xa0sequences \
\"\u{1f60a}\".\
";

Raw strings

They are double-quoted strings with the r prefix.
You can add as many # as needed as a prefix and suffix.
You can't use escape sequences in raw strings.
They can span multiple lines.
let text: &str = r"Multi
line.";
let text: &str = r#"Inner "double quotes""#;
let text: &str =
r###"Text with "quotes" and ##hashes##."###;

Byte strings

They are prefixed with a b.
You can also use raw byte strings prefixed with br.
Their content doesn't need to be valid UTF-8.
let bytes: &[u8] = b"abc\ndef\x00";
let bytes: &[u8] = br#""quoted""#;

Tuples

A tuple is a group of unamed values.
The size and types are part of the type, they can't be modified.
You can access the values by destructuring or by index (0-based).
let tuple: (usize, usize, bool, &str) = (1_usize, 4_usize, true, "one");
let (first, _, third, ..) = tuple;
assert_eq!(first, tuple.0);
assert_eq!(third, tuple.2);
An empty tuple () represents the Unit type (void).

Arrays

An array is an ordered group of elements of the same type.
The size and element type are part of the array type, they can't be modified.
You can access the values by destructuring or by index (0-based).
Elements are stored in contiguous memory.
let array: [usize; 5] = [1_usize, 4_usize, 6_usize, 0_usize, 1_usize];
let [first, _, third, .., last] = array;
assert_eq!(5, array.len());
assert_eq!(first, array[0]);
assert_eq!(Some(&third), array[2]);
assert_eq!(Some(&last), array.last());

Slices

Slices are references to a section of an array.
Unlike arrays, their size doesn't need to be known at compile time.
You can access the values by index (0-based).
let array = ['a', 'b', 'c', 'd', 'e', 'f'];
let slice: &[char] = array.as_slice();
let slice: &[char] = &array;
let slice: &[char] = &array[..];
let first_two = &array[..2];
let next_two = &array[2..4];
let skip_first_two = &array[2..];
let index_one_to_three_inclusive = &array[1..=3];
assert_eq!(array.len(), slice.len());
assert_eq!('c', next_two[0]);
assert_eq!(&['c', 'd'], next_two);

Structs

The convention is to use PascalCase for the struct name.

Named tuples

struct Measure(f64, UnitOfMeasure);
let height = Measure(1.8, UnitOfMeasure::Meter);
assert_eq!(1.8, height.0);

Unit (empty) structs

They can be used as marker types, to hold implementation, or to encode state into the type system.
They are zero-sized, meaning that the compiler will optimize them out.
#[derive(Debug)]
struct Empty;
let empty> = Empty;
println!("{empty:?}");

Structs with named fields

The convention is to use snake_case for field names.
struct Coordinate {
x: f64,
y: f64,
projection: Projection,
}
let x = 2.5;
let coord = Coordinate { projection: Projection::Mercator, x, y: 1.5 };
assert_eq!(x, coord.x);
assert_eq!(1.5, coord.y);

Enums

An Enum is a group of named variants that can have different types.
The convention is to use PascalCase for the enum name and the variant names.
enum Event {
Focus,
Keypress(char),
Click { x: f32, y: f32 },
}
let event = Event::Click { x: 1.0, y: 2.0 };
assert!(matches!(event, Event::Click { x: _, y: 2.0 }));

Results

#[must_use]
#[derive(Copy, Debug, Hash, PartialEq, PartialOrd, Eq, Ord)]
enum Result<T, E> {
Ok(T),
Err(E),
}

Options

Pattern matching

Ownership

Functions

Traits

impl blocks

Associated types

Loops

Async

Derives

Generics

Dynamic dispatch

Lifetimes

Modules

Imports

Attributes

Declarative macros

Procedural macros

Tests

Unsafe

Table of content