Types & Traits
Believe the type
Rust is statically typed, but its type inference is so good that most of the
time you barely notice. You’ll write let x = 42; and the compiler will quietly
work out “ah, that’s an i32.” You’ll write let xs = vec![1.0, 2.0, 3.0];
and it’ll figure out Vec<f64>.
But when you write functions, you have to be explicit, because the function signature is a contract — both the compiler and the next person reading your code rely on it.
The primitive types you’ll meet most:
- Integers —
i8i16i32i64i128and their unsigned cousinsu8…u128. Plusisize/usize, sized to your CPU’s pointer width. - Floats —
f32andf64. Usef64unless you have a reason not to. bool—trueorfalse. No 0/1 nonsense; a bool is its own type.char— a single Unicode scalar value. 4 bytes.'🦀'is achar.&strandString— string slice and owning string respectively.
Structs — your own nouns
When you have a few values that always belong together, make a struct:
Two convenient variations: tuple structs (a struct that’s just a named tuple) and unit structs (no fields — useful for marker types).
Methods with impl
You attach behaviour to a struct in an impl block:
Rect::new is called with :: because it’s an associated function — it
doesn’t operate on an existing Rect. Methods like .area() take self
(or &self or &mut self) and are called with ..
Enums — when something is one of several things
An enum in Rust is much more like Haskell’s data types or TypeScript’s
tagged unions than like C’s enums. Each variant can carry its own data.
This is one of the most distinctive features of Rust. You’ll use enum all
the time — and the standard library’s two most-used types, Option and
Result, are both enums. (More on those in the error-handling chapter.)
Traits — shared behaviour
A trait is like an interface in Java, a protocol in Swift, or a typeclass
in Haskell — it’s a set of methods a type promises to implement. You define
the trait, then impl Trait for Type to make a type implement it.
Generic functions with trait bounds
You can write functions that work with any type that implements a trait:
The <T: Greet> reads as “for any type T that implements Greet.” There’s
also impl Trait syntax for the same idea, which reads nicer for simple cases:
fn announce(thing: &impl Greet) { /* … */ }Derive — free implementations
Tons of common traits can be auto-implemented with #[derive(…)]:
Debug gives you {:?} formatting. Clone gives you .clone(). PartialEq
gives you == and !=. There are dozens of derivable traits; you’ll meet
Copy, Default, Hash, Eq, Ord soon.
That’s the whirlwind tour. Next: pattern matching, which is where the enums above start to really earn their keep.