Rust
A quick language tour
Last update:
2026-02Official websites and logos
Official logo
Unofficial mascot
Ferris the crab
Dependency registry
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
rustupBuild system and package manager
cargoIncludes 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.
Examples
Update everything:
rustup updateInstall wasm target (minimum tools):
rustup toolchain install stable --profile minimal --target wasm32-unknown-unknownSet default to the nightly channel:
rustup default nightlyShow installed components and defaults:
rustup showChannels
stablebetanightly
(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.
Project management
Creates a library project:
cargo init --libPrints a tree of dependencies:
cargo treeAdds "tokio" 1.x as a dependency with the "full" feature:
cargo add tokio@1 --features fullUpdates
Cargo.lock with the latest versions of the dependencies that still satisfy the constraints: cargo updateCompiler commands
Check for compile errors and warnings:
cargo check --all-features --all-targetsApply fixes suggested by the compiler to fix warnings:
cargo fix --all-features --all-targets --broken-codeCreates a release binary with all the features enabled:
cargo build --release --features fullClippy
Run the linter:
cargo clippyApply suggested fixes:
rustup clippy --fixTests
Run all the tests:
cargo test --all-features --no-fail-fastRun a single "example" test with stdout output:
cargo test example -- --no-captureRun commands
Build and run the "demo" example in release mode:
cargo run --release --example demoProject structure
-
.cargo
- config.toml
-
examples
-
example1.rs
- main.rs
- utils.rs
- example2.rs
-
example1.rs
-
src
-
bin
-
bin1
- utils.rs
- main.rs
- bin2.rs
-
bin1
-
mod1
- mod.rs
- utils.rs
- lib.rs
- main.rs
- mod2.rs
-
bin
- target
-
tests
-
integration-test1.rs
- main.rs
- utils.rs
- integration-test2.rs
-
integration-test1.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
The package
[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.
Crates using different editions can still depend on each other.
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 can enable other features, optional dependencies, and even dependency features.
features. They specify which ones are enabled by default.Features can enable other features, optional dependencies, and even dependency features.
For the project
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
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.
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
unsafeblock, 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, f64Boolean
boolCharacter
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.
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).
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.
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).
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),
}
Comments
Line comments
// This is a line commentlet 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
rustdocto generate the documentation.Those examples can use dev dependencies, and are treated as tests.
/// Struct description
/// # Example
/// ```
/// let example = Example;
/// ```
struct Example;