Skip to content

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.

🦀 main.rs

The primitive types you’ll meet most:

  • Integersi8 i16 i32 i64 i128 and their unsigned cousins u8u128. Plus isize/usize, sized to your CPU’s pointer width.
  • Floatsf32 and f64. Use f64 unless you have a reason not to.
  • booltrue or false. No 0/1 nonsense; a bool is its own type.
  • char — a single Unicode scalar value. 4 bytes. '🦀' is a char.
  • &str and String — string slice and owning string respectively.

Structs — your own nouns

When you have a few values that always belong together, make a struct:

🦀 main.rs

Two convenient variations: tuple structs (a struct that’s just a named tuple) and unit structs (no fields — useful for marker types).

🦀 main.rs

Methods with impl

You attach behaviour to a struct in an impl block:

🦀 main.rs

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.

🦀 main.rs

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.

🦀 main.rs

Generic functions with trait bounds

You can write functions that work with any type that implements a trait:

🦀 main.rs

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(…)]:

🦀 main.rs

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.