Fields through references requires unsafe because the compiler cannot guarantee alignment for Dereferences. Use #[repr(packed(2))] or similar to specify minimum alignment.
By default, all struct fields are private (visible only within the module where the struct is Defined). Use pub to make fields public:
pub radius : f64 , // public — accessible from other modules
center : Point , // private — only accessible within this module
pub fn new (radius : f64 , center : Point ) -> Self {
Circle { radius, center }
pub fn area ( & self ) -> f64 {
std :: f64 :: consts :: PI * self . radius * self . radius
Note: making a struct pub does not make its fields pub. Each field must be individually marked As pub. This is different from C++ where public: in a class definition makes all subsequent Members public.
Methods are defined inside impl blocks:
// Associated function (no self parameter) — like a static method
fn new (width : f64 , height : f64 ) -> Self {
Rectangle { width, height }
fn square (size : f64 ) -> Self {
Rectangle { width : size, height : size }
// Method taking immutable reference
// Method taking mutable reference
fn scale ( &mut self , factor : f64 ) {
// Method taking ownership
fn into_components ( self ) -> ( f64 , f64 ) {
( self . width, self . height)
let r = Rectangle :: new ( 3.0 , 4.0 );
assert_eq! (r . area (), 12.0 );
A type can have multiple impl blocks. This is useful for organizing methods by functionality or For separating trait implementations from inherent methods:
fn area ( & self ) -> f64 { self . width * self . height }
fn perimeter ( & self ) -> f64 {
2.0 * ( self . width + self . height)
Rust uses static dispatch by default — the compiler knows the exact type at the call site and Monomorphizes the code. Trait methods called through dyn Trait use dynamic dispatch via vtable Indirection.
struct Circle { radius : f64 }
fn area ( & self ) -> f64 { std :: f64 :: consts :: PI * self . radius * self . radius }
// Static dispatch — no vtable
let c = Circle { radius : 5.0 };
let a = c . area (); // compiler generates Circle::area directly
// Dynamic dispatch — vtable lookup
let s : &dyn Shape = & Circle { radius : 5.0 };
let a = s . area (); // indirect call through vtable
Enums are algebraic data types (sum types). Each variant can optionally carry data. Rust enums are Discriminated unions — the compiler stores a tag (discriminant) to identify which variant is active.
let d = Direction :: North ;
let home = IpAddr :: V4 ( 127 , 0 , 0 , 1 );
let loopback = IpAddr :: V6 ( String :: from ( "::1" ));
Rectangle { width : f64 , height : f64 },
Point { x : f64 , y : f64 },
let s = Shape :: Circle { radius : 5.0 };
Resize { width : u32 , height : u32 }, // struct
The compiler stores a discriminant tag alongside the variant data. The default discriminant type is The smallest integer that can represent all variants:
// sizeof(Color) == 1 (tag only, no data)
Integer ( i64 ), // 1 — 8 bytes of data
Text ( String ), // 2 — 24 bytes (ptr + len + cap)
// sizeof(Payload) == 32 (8 bytes tag + 24 bytes data, with padding)
You can control the discriminant with #[repr]:
Enums that carry data are one of Rust’s most powerful features. The standard library’s Option and Result are both enums:
Custom enums with data are equally powerful:
Add ( Box < Expr >, Box < Expr >),
Mul ( Box < Expr >, Box < Expr >),
fn eval (expr : & Expr , env : & std :: collections :: HashMap < String , i64 >) -> i64 {
Expr :: Add (l, r) => eval (l, env) + eval (r, env),
Expr :: Mul (l, r) => eval (l, env) * eval (r, env),
Expr :: Var (name) => env . get (name) . copied () . unwrap_or ( 0 ),
Note the use of Box<Expr> — without boxing, the enum would be infinitely sized because Expr Contains itself recursively.
Pattern matching is Rust’s primary control flow mechanism for enums and is exhaustively checked by The compiler.
fn color_to_rgb (c : Color ) -> ( u8 , u8 , u8 ) {
Color :: Red => ( 255 , 0 , 0 ),
Color :: Green => ( 0 , 255 , 0 ),
Color :: Blue => ( 0 , 0 , 255 ),
The compiler verifies that every variant is handled. If you add a new variant to ColorEvery match on Color will produce a compile error until updated. This is exhaustive pattern matching.
Match ergonomics automatically add & and ref patterns when matching through references:
Color :: Red => println! ( "red" ),
// Before match ergonomics: &Color::Red => ...
// Now: Color::Red => ... (compiler auto-refs)
A match guard is an additional if condition on a match arm:
Some (x) if x < 5 => println! ( "less than five: {}" , x),
Some (x) => println! ( "{}" , x),
None => println! ( "none" ),
Match guards do not participate in exhaustiveness checking. The compiler cannot prove that a guard Will always match for a given variant, so you may still need a catch-all arm.
struct Point { x : i32 , y : i32 }
let p = Point { x : 1 , y : 2 };
Point { x : 0 , y : 0 } => println! ( "origin" ),
Point { ref x, ref y } => println! ( "x={}, y={}" , x, y),
// x and y are &i32, p is not moved
let mut v = vec! [ 1 , 2 , 3 ];
ref mut v => v . push ( 4 ), // borrow v mutably and push
assert_eq! (v, vec! [ 1 , 2 , 3 , 4 ]);
The @ operator binds a value to a name while also testing it against a pattern:
n @ 1 ..= 12 => println! ( "child of age {}" , n),
n @ 13 ..= 19 => println! ( "teenager of age {}" , n),
n => println! ( "adult of age {}" , n),
This is especially useful when you need to destructure and also capture the whole value:
let v = Value :: Number ( 42 );
Value :: Number (n @ 0 ..= 100 ) => println! ( "small number: {}" , n),
Value :: Number (n) => println! ( "large number: {}" , n),
Value :: Text (s) => println! ( "text: {}" , s),
1 ..= 5 => println! ( "one through five" ),
6 ..= 10 => println! ( "six through ten" ),
_ => println! ( "something else" ),
Range patterns only work on numeric types and char. They are inclusive on both ends.
( 0 , y) => println! ( "x is zero, y is {}" , y),
(x, 0 ) => println! ( "x is {}, y is zero" , x),
(x, y) if x == y => println! ( "equal: {}" , x),
(x, y) => println! ( "different: {} and {}" , x, y),
struct Point { x : f64 , y : f64 }
let p = Point { x : 0.0 , y : 7.0 };
Point { x : 0.0 , y } => println! ( "on the y-axis at {}" , y),
Point { x, y : 0.0 } => println! ( "on the x-axis at {}" , x),
Point { x, y } => println! ( "at ({}, {})" , x, y),
When you only care about one variant, if let is more concise than match:
let some_value = Some ( 7 );
if let Some (n) = some_value {
println! ( "value is {}" , n);
if let does not check exhaustiveness. The else branch handles all non-matching cases:
if let Some (n) = some_value {
println! ( "value is {}" , n);
let-else combines pattern matching with early return:
fn process (data : Option < Vec < i32 >>) -> i32 {
let Some (values) = data else {
The else block must diverge (return, break, continue, panic, or loop). This is cleaner than the Equivalent match with a single arm and a fallback.
let mut stack = Vec :: new ();
while let Some (top) = stack . pop () {
while let runs the loop body as long as the pattern matches. When it stops matching, the loop Ends.
The matches! macro is a concise way to check whether a value matches a pattern:
assert! ( matches! (x, Some ( 5 )));
assert! ( matches! (x, Some (_)));
assert! ( ! matches! (x, None ));
assert! ( matches! (x, Some (n) if n > 3 ));
fn maybe_double (x : Option < i32 >) -> Option < i32 > {
fn parse_int (s : & str ) -> Result < i32 , std :: num :: ParseIntError > {
In practice, you rarely write explicit match for Option and Result. Combinator methods and the ? operator are more idiomatic:
fn maybe_double (x : Option < i32 >) -> Option < i32 > {
fn parse_and_double (s : & str ) -> Result < i32 , std :: num :: ParseIntError > {
The #[derive] attribute auto-generates implementations for common traits:
#[derive( Debug , Clone , PartialEq , Eq , Hash )]
Trait What it generates Debugfmt::Debug for {:?} formattingCloneclone() — deep copy (requires all fields to be Clone)CopyImplicit bitwise copy (requires CloneNo Drop) PartialEq== and != — structural equalityEqMarks type as having reflexive equality (requires PartialEq) PartialOrd<``>``<=``>= — derived from field orderOrdTotal ordering (requires PartialOrd``Eq) HashHash function for HashMap/HashSet keys DefaultDefault value (all fields must implement Default)