Skip to content
Learn Rust
Theme

Rust syntax cheat sheet

Print this page: use your browser’s Print command (Ctrl+P or Cmd+P), or choose “Save as PDF” in the print dialog. Every error code, and the program behind each fix, comes from a run in Rust 1.99.1, edition 2024; the collapsed “Runs” boxes show the programs and what they printed. Cells with .. or ..., and the signature without its body, are elided placeholders, not runs. This sheet links the lessons instead of teaching again.

Symbol Meaning Lesson
let, mut bind a name; fixed unless mut Variables
fn, -> define a function; the result type follows -> Functions
; ends a statement and drops its value Statements
{ } a block; its last line, with no ;, is its value Statements
( ) call, grouping, tuple (1, 'x'); () is “nothing” Tuples
[ ] index v[0], array [1, 2], slice type &[T] Vectors
:: a path: module, type or function Signatures
. a field or a method of a value Methods
&, &mut borrow to read; borrow to change Borrowing, Mutable borrows
* follow a reference *m; also multiply Borrowing
'a, 'outer: a lifetime; a loop label Lifetimes, Loops
! a macro call (println!); also “not”: !done Macros and attributes
? on Err or None, return it now; else the value inside. The function must return a Result or Option Errors
#[...] an attribute for the next item Macros and attributes
|x| a closure (an inline function) Closures
=> a match arm: pattern, then result Match
.., ..= range without or with its end; “the rest” in a pattern Loops
_ ignore this; digit separator 1_000 Patterns
@ name a value and test it: n @ 3..=9 Patterns
<T>, where a type parameter; its bounds after the signature Generics
as convert a number: 7 as f64 Declarations and types
impl add methods, or implement a trait for a type Methods, Traits
dyn a trait object: the method is found at run time Trait objects
pub, use, mod visible outside; shorten a name; a module Modules
/// a doc comment on the next item Macros and attributes
Runs: one program that uses every symbol above
Rustsrc/bin/symbols.rs
//! A crate-level `//!` doc comment.
#![allow(dead_code, unused_variables)]
mod shapes {
/// A doc comment on a public item (`///`).
pub struct Square(pub f64);
pub trait Area {
fn area(&self) -> f64;
}
impl Area for Square {
fn area(&self) -> f64 {
self.0 * self.0
}
}
}
use shapes::{Area, Square};
fn biggest<T>(items: &[T]) -> &T
where
T: PartialOrd,
{
let mut best = &items[0];
for item in items {
if item > best {
best = item;
}
}
best
}
fn longer<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() >= b.len() { a } else { b }
}
fn total(shapes: &[&dyn Area]) -> f64 {
shapes.iter().map(|s| s.area()).sum()
}
fn parse(text: &str) -> Result<i32, std::num::ParseIntError> {
let n = text.parse::<i32>()?;
Ok(n)
}
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let mut count = 0;
count += 1;
let big = 1_000_000;
let pair = (1, 'x');
let list = [1, 2, 3];
let first = list[0];
let has_items = !list.is_empty();
let r = &count;
let m = &mut count;
*m += 1;
let half = 7 as f64 / 2.0;
let label = 'x';
'outer: for i in 0..3 {
for j in 0..=3 {
if j == 1 {
continue 'outer;
}
}
}
let kind = match first {
1 | 2 => "small",
n @ 3..=9 => "mid",
_ => "other",
};
let p = Point { x: 1, y: 2 };
let Point { x, .. } = p;
let sq = Square(2.0);
let sum = total(&[&sq]);
println!("{} {} {} {:?} {} {} {}", count, first, half, parse("7"), kind, x, sum);
println!("{} {}", has_items, longer("ab", "abc"));
println!("{}", biggest(&[3, 9, 4]));
}
$ cargo run --bin symbols
2 1 3.5 Ok(7) small 1 4
true abc
9

Syntax errors stop at the parser and have no code. The others carry the code shown.

You write Code In Rust
x++; none x += 1;
let n = big ? 10 : 1; none let n = if big { 10 } else { 1 };
for (let mut i = 0; i < 3; i++) none for i in 0..3, (0..9).step_by(3)
let a = null; and let b: String = None; E0425, E0308 let b: Option<String> = None;, then match or if let
let wide: i64 = small; (an i32) E0308 i64::from(small), f64::from(small) * 1.5
area(width = 3, height = 4) E0425 a struct: Rect { width: 3, height: 4 }
two fn show, one per type E0428, E0308 one trait, impl Show for i32, impl Show for &str
switch (n) { case 1: ... break; } none match n { 1 => .., 2 | 3 => .., _ => .. }
try { ... } catch (e) { ... } none return a Result, pass it up with ?
if count { ... } (an integer) E0308 count != 0, !items.is_empty()
let first = s[0]; (a String) E0277 s.chars().next(), s.as_bytes()[0], &s[0..1] (panics inside a character)
n == 1.0 (n an integer) E0277, E0308 n == 1, or f64::from(n) == 1.0 (n an i32)
a === b none ==, between two values of one type

Taught in what Rust doesn’t have, with the null, string indexing and overloading details in their own lessons. All codes: error index.

Runs: each failing line compiled, and the fixes
terminal
$ for f in fails/*.rs; do rustc --edition 2024 $f; done   # first lines of each error
== fails/e01.rs
error[E0308]: mismatched types
== fails/h01.rs
error: Rust has no postfix increment operator
== fails/h02.rs
error: Rust has no ternary operator
== fails/h03.rs
error: expected pattern, found `let`
error: expected one of `)`, `,`, `@`, `if`, or `|`, found `=`
== fails/h04.rs
error[E0425]: cannot find value `null` in this scope
error[E0308]: mismatched types
== fails/h05.rs
error[E0308]: mismatched types
== fails/h06.rs
error[E0425]: cannot find value `width` in this scope
error[E0425]: cannot find value `height` in this scope
== fails/h07.rs
error[E0428]: the name `show` is defined multiple times
error[E0308]: mismatched types
== fails/h08.rs
error: expected one of `.`, `;`, `?`, `}`, or an operator, found `{`
== fails/h09.rs
error: keyword `catch` cannot follow a `try` block
== fails/h10.rs
error[E0308]: mismatched types
== fails/h11.rs
error[E0277]: the type `str` cannot be indexed by `{integer}`
== fails/h12.rs
error[E0277]: can't compare `{integer}` with `{float}`
error[E0308]: mismatched types
== fails/h12b.rs
error: invalid comparison operator `===`
Rustsrc/bin/fixes.rs
use std::num::ParseIntError;
struct Rect {
width: u32,
height: u32,
}
fn area(r: &Rect) -> u32 {
r.width * r.height
}
trait Show {
fn show(&self) -> String;
}
impl Show for i32 {
fn show(&self) -> String {
format!("int {self}")
}
}
impl Show for &str {
fn show(&self) -> String {
format!("text {self}")
}
}
fn parse(s: &str) -> Result<i32, ParseIntError> {
let n: i32 = s.parse()?;
Ok(n)
}
fn main() {
// 1 x++
let mut x = 5;
x += 1;
println!("1 x += 1 -> {x}");
// 2 c ? a : b
let big = true;
let n = if big { 10 } else { 1 };
println!("2 if big {{ 10 }} else {{ 1 }} -> {n}");
// 3 for (i = 0; i < 3; i++)
let mut seen = Vec::new();
for i in 0..3 {
seen.push(i);
}
let stepped: Vec<i32> = (0..9).step_by(3).collect();
println!("3 0..3 -> {seen:?}, (0..9).step_by(3) -> {stepped:?}");
// 4 null
let name: Option<String> = None;
let shown = match &name {
Some(s) => s.as_str(),
None => "(none)",
};
println!("4 Option<String> None -> {shown}");
// 5 implicit numeric conversion
let small: i32 = 7;
let wide: i64 = i64::from(small);
let scaled = f64::from(small) * 1.5;
println!("5 i64::from(7) -> {wide}, f64::from(7) * 1.5 -> {scaled}");
// 6 named arguments
let r = Rect { width: 3, height: 4 };
println!("6 area(&Rect {{ width: 3, height: 4 }}) -> {}", area(&r));
// 7 overloading
println!("7 1.show() -> {}, \"a\".show() -> {}", 1.show(), "a".show());
// 8 switch with fallthrough
for n in [1, 2, 3, 9] {
let word = match n {
1 => "one",
2 | 3 => "two or three",
_ => "many",
};
println!("8 match {n} -> {word}");
}
// 9 try / catch
println!("9 parse(\"42\") -> {:?}", parse("42"));
println!("9 parse(\"x\") -> {:?}", parse("x"));
// 10 truthiness
let count = 3;
let items: Vec<i32> = Vec::new();
println!("10 count != 0 -> {}, !items.is_empty() -> {}", count != 0, !items.is_empty());
// 11 string indexing
let s = String::from("héllo");
println!(
"11 chars().next() -> {:?}, as_bytes()[0] -> {}, &s[0..1] -> {:?}",
s.chars().next(),
s.as_bytes()[0],
&s[0..1]
);
// 12 == across types
let k = 1;
println!("12 k == 1 -> {}, f64::from(k) == 1.0 -> {}", k == 1, f64::from(k) == 1.0);
println!("12 String == &str -> {}", String::from("a") == "a");
}
$ cargo run --bin fixes
1  x += 1            -> 6
2  if big { 10 } else { 1 } -> 10
3  0..3 -> [0, 1, 2], (0..9).step_by(3) -> [0, 3, 6]
4  Option<String> None -> (none)
5  i64::from(7) -> 7, f64::from(7) * 1.5 -> 10.5
6  area(&Rect { width: 3, height: 4 }) -> 12
7  1.show() -> int 1, "a".show() -> text a
8  match 1 -> one
8  match 2 -> two or three
8  match 3 -> two or three
8  match 9 -> many
9  parse("42") -> Ok(42)
9  parse("x")  -> Err(ParseIntError { kind: InvalidDigit })
10 count != 0 -> true, !items.is_empty() -> false
11 chars().next() -> Some('h'), as_bytes()[0] -> 104, &s[0..1] -> "h"
12 k == 1 -> true, f64::from(k) == 1.0 -> true
12 String == &str -> true
  1. The last line of a block, with no ;, is its value.
  2. A ; drops the value, so the block is worth ().
  3. if, match and loop are expressions: let c = if a > 10 { "big" } else { "small" };.
  4. Assignment and += are worth ().
  5. A function body is a block, so no return is needed at the end. n * 2; there is E0308.

See Statements and expressions.

Runs: rules 1 to 5
Rustsrc/bin/exprs.rs
fn tail(n: i32) -> i32 {
n * 2
}
fn main() {
// 1 a block is worth its last line
let a = {
let w = 3;
w * 4
};
// 2 a trailing ; makes it worth ()
let b = {
let w = 3;
let _ = w * 4;
};
// 3 if, match and loop are expressions
let c = if a > 10 { "big" } else { "small" };
let d = match a {
12 => "twelve",
_ => "other",
};
let mut k = 0;
let e = loop {
k += 1;
if k == 3 {
break k * 10;
}
};
// 4 assignment is worth ()
let mut m = 0;
let f = m += 5;
// 5 the function body is a block: no return needed
println!("1 {a}");
println!("2 {b:?}");
println!("3 {c} {d} {e}");
println!("4 {f:?} {m}");
println!("5 {}", tail(4));
}
$ cargo run --bin exprs
1  12
2  ()
3  big twelve 30
4  () 5
5  8
pub fn longest_by<'a, T, K>(items: &'a [T], key: impl Fn(&T) -> K) -> Option<&'a T>
where
K: Ord + Display,
Piece Reads as
pub fn longest_by a public function
<'a, T, K> placeholders: a lifetime and two types, filled in by the call
items: &'a [T] a borrowed slice of any T; the caller keeps the list
key: impl Fn(&T) -> K any closure that takes &T and returns a K
-> Option<&'a T> maybe one item, borrowed from items for 'a; None if the slice is empty
where K: Ord + Display K must be orderable and printable

More in Reading a signature.

Runs: this signature, called
Rustsrc/bin/sig.rs
use std::fmt::Display;
// pub fn name <generics> (parameters) -> result where bounds
pub fn longest_by<'a, T, K>(items: &'a [T], key: impl Fn(&T) -> K) -> Option<&'a T>
where
K: Ord + Display,
{
let mut best: Option<&T> = None;
for item in items {
match best {
Some(b) if key(b) >= key(item) => {}
_ => best = Some(item),
}
}
best
}
fn main() {
let words = ["fig", "banana", "kiwi"];
println!("{:?}", longest_by(&words, |w| w.len()));
let none: [&str; 0] = [];
println!("{:?}", longest_by(&none, |w| w.len()));
}
$ cargo run --bin sig
Some("banana")
None

Left column: the format string. Right: what format! made of it, between bars. {} is for people, {:?} for programmers; {:#?} spreads over lines. A spec is {, a name or position, :, then fill, alignment (< ^ >), sign (+), #, 0, width, .precision and a type (?, x, b), then }; 1$ takes the width from argument 1. Syntax: Format strings; more in Formatting.

terminal
$ cargo run --bin fmt
{}           |Ana|
{:?}         |"Ana"|
{:?}         |P { x: 1, y: 2 }|
{:>8}        |     Ana|
{:<8}        |Ana     |
{:^8}        |  Ana   |
{:*^9}       |***Ana***|
{:08.3}      |0003.142|
{:.2}        |3.14|
{:+}         |+5|
{:5}         |   42|
{:#x}        |0xff|
{:#010b}     |0b00000101|
{name}       |Ana|
{0}-{0}-{1}  |a-a-b|
{{}}         |{}|
{:>1$}       |   Ana|
P {
    x: 1,
    y: 2,
}
Runs: the program
Rustsrc/bin/fmt.rs
// Prints each format string next to what it produced, between |bars|.
macro_rules! row {
($fmt:literal $(, $arg:expr)*) => {
println!("{:<12} |{}|", $fmt, format!($fmt $(, $arg)*))
};
}
#[derive(Debug)]
#[allow(dead_code)]
struct P {
x: i32,
y: i32,
}
fn main() {
let name = "Ana";
let pi = 3.14159;
let p = P { x: 1, y: 2 };
row!("{}", name);
row!("{:?}", name);
row!("{:?}", p);
row!("{:>8}", name);
row!("{:<8}", name);
row!("{:^8}", name);
row!("{:*^9}", name);
row!("{:08.3}", pi);
row!("{:.2}", pi);
row!("{:+}", 5);
row!("{:5}", 42);
row!("{:#x}", 255);
row!("{:#010b}", 5);
row!("{name}");
row!("{0}-{0}-{1}", "a", "b");
row!("{{}}");
row!("{:>1$}", name, 6);
println!("{:#?}", p);
}
What Style Example
variables, functions, methods, modules, fields snake_case user_count, parse_line
structs, enums, traits, variants UpperCamelCase UserAccount, Shape::Circle
constants, statics SCREAMING_SNAKE_CASE MAX_USERS
type parameters, lifetimes short UpperCamelCase, often T; short lowercase T, 'a
method prefixes as_ borrows, to_ makes a new value, into_ consumes as_str, to_uppercase, into_bytes

The style rules are lints (non_snake_case and others); the prefixes come from the API Guidelines. See Naming.

Runs: every name above, with #![deny(nonstandard_style)]
Rustsrc/bin/naming.rs
#![deny(nonstandard_style)]
const MAX_USERS: u32 = 3;
struct UserAccount {
user_count: u32,
}
enum Shape {
Circle(f64),
Square(f64),
}
trait Describe {
fn describe(&self) -> String;
}
impl Describe for Shape {
fn describe(&self) -> String {
match self {
Shape::Circle(r) => format!("circle {r}"),
Shape::Square(s) => format!("square {s}"),
}
}
}
mod parse_line {
pub fn first_word<'a, T: AsRef<str> + ?Sized>(s: &'a T) -> &'a str {
s.as_ref().split(' ').next().unwrap_or("")
}
}
fn main() {
let account = UserAccount { user_count: MAX_USERS };
println!("{} {}", account.user_count, parse_line::first_word("hi there"));
println!("{} {}", Shape::Circle(1.5).describe(), Shape::Square(2.0).describe());
// method-name prefixes from the standard library
let s = String::from("Hi");
let v = vec![1, 2];
println!("{} {} {}", s.as_str(), s.to_uppercase(), v.is_empty());
println!("{:?}", s.into_bytes());
}
$ cargo run --bin naming
3 hi
circle 1.5 square 2
Hi HI false
[72, 105]