Syntax and ownership
Bindings, the primitive type set, and the ownership and borrowing rules that replace a garbage collector.
Values, bindings and functions
Bindings are immutable unless you write mut. The compiler infers types where it can and requires an annotation where it cannot.
fn main() {
let count = 3; // immutable by default
let mut total = 0; // mut allows reassignment
total += count;
let ratio: f64 = 0.75; // annotation when inference is not enough
let pair = (1, "two"); // tuple: fixed size, mixed types
let (a, b) = pair; // destructuring
println!("{} {} {} {:?}", total, ratio, a, pair.1);
}
fn largest(list: &[i32]) -> i32 {
let mut max = list[0];
for n in list {
if *n > max {
max = *n;
}
}
max
}| Type | Notes |
|---|---|
i32, u64, usize | Integers; i32 is the default, usize for indexing |
f64 | Floating point; the default |
bool | true or false |
char | One Unicode scalar value, written with single quotes |
String | Owned, growable, heap-allocated text |
&str | A borrowed view of text; no ownership, no allocation |
Vec<T> | Growable array, Vec::new() or vec![1, 2, 3] |
Option<T> | Some(T) or None; there is no null |
💡
println! is a macro, not a function — that is what the ! means. {} uses the Display trait and {:?} uses Debug, which is what you get for free from #[derive(Debug)].Ownership and borrowing
fn main() {
let s = String::from("hello");
let t = s; // move: s is no longer usable
println!("{}", t);
let u = t.clone(); // explicit deep copy when you need both
println!("{} {}", t, u);
let len = measure(&t); // borrow instead of moving
println!("{} has {} bytes", t, len);
}
fn measure(s: &str) -> usize {
s.len()
}
fn push_word(v: &mut Vec<String>) {
v.push(String::from("word"));
}
// lifetimes are usually elided; name them when the output borrows an input
fn first<'a>(words: &'a [String]) -> &'a str {
words[0].as_str()
}- Every value has exactly one owner, and when the owner goes out of scope the value is dropped — that is the whole memory management strategy, with no garbage collector.
- Assignment moves heap types such as
StringandVec<T>, and copies simpleCopytypes such asi32andbool. - A shared borrow
&Tallows read access without a move; a mutable borrow&mut Tgrants exclusive write access. - You may have any number of shared borrows or exactly one mutable borrow, never both at the same time. That single rule removes data races at compile time.
- The borrow checker rejects the program, not the runtime: if it compiles, the reference it hands out is still valid when it is used.
FAQ
Why does the compiler say 'value borrowed here after move'?
Assignment moved the
String into the new binding, so the old name is dead. Pass a reference with &s, or call .clone() when both bindings really need to own the data.When is clone the right answer?
When two owners genuinely need independent copies, or when a borrow would force lifetimes through an API that does not care about them. It is a real allocation, so in a hot loop it is worth restructuring first.
Related
Structs, enums and pattern matching Error handling with Result and Cargo
Last refreshed 2026-09-18.