iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Structs

Structs group named fields under a single type. Rust gives you three flavours — classic, tuple, and unit — plus derive macros for the common traits (Debug, Clone, PartialEq) that make idiomatic Rust code so terse.

Define, methods, traits, derive, update

EXAMPLE
// 1) Classic struct
struct Point {
    x: f64,
    y: f64,
}

fn main() {
    let p = Point { x: 1.0, y: 2.0 };
    println!("({}, {})", p.x, p.y);
}

// 2) Tuple struct — fields by position, no names
struct Color(u8, u8, u8);
let red = Color(255, 0, 0);
println!("R={}", red.0);

// Useful for newtype patterns:
struct UserId(u64);
let uid = UserId(42);

// 3) Unit struct — zero fields, useful for markers / state types
struct Authenticated;
struct Anonymous;

struct Session<S> { user_id: Option<u64>, _state: std::marker::PhantomData<S> }

// 4) Derive common traits
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct Coord {
    x: i32,
    y: i32,
}

fn main2() {
    let a = Coord { x: 1, y: 2 };
    let b = a;                          // Copy types are duplicated automatically
    println!("{:?}", a);                // 'Coord { x: 1, y: 2 }'
    println!("{:?}", a == b);           // true
}

// Copy is for cheap-to-copy fields only (no allocations).
// Clone is required for everything else; .clone() explicitly duplicates.

// 5) Methods — impl block
impl Coord {
    fn new(x: i32, y: i32) -> Self {            // associated function (constructor-style)
        Self { x, y }
    }

    fn distance(&self, other: &Coord) -> f64 {  // method
        let dx = (self.x - other.x) as f64;
        let dy = (self.y - other.y) as f64;
        (dx * dx + dy * dy).sqrt()
    }

    fn translate(&mut self, dx: i32, dy: i32) {  // mutable method
        self.x += dx;
        self.y += dy;
    }
}

fn main3() {
    let mut p = Coord::new(1, 2);
    p.translate(3, 4);
    let q = Coord::new(0, 0);
    println!("distance = {}", p.distance(&q));
}

// 6) Struct update syntax — clone with overrides
let base = Coord { x: 1, y: 2 };
let shifted = Coord { x: 10, ..base };    // y: 2 from base, x: 10 overridden

// 7) Field init shorthand
fn make(name: String, age: u32) -> Person {
    Person { name, age }                  // shorthand when field name == variable name
}

struct Person { name: String, age: u32 }

// 8) Public vs private fields
pub struct ApiResponse {
    pub status: u16,
    pub headers: Vec<(String, String)>,
    pub(crate) raw_body: Vec<u8>,        // visible inside crate only
    cookies: Vec<String>,                  // module-private
}

// Convention: keep fields private; expose getters/setters or builder pattern.

// 9) Builder pattern
pub struct HttpClient {
    timeout: std::time::Duration,
    retries: u32,
    user_agent: String,
}

pub struct HttpClientBuilder {
    timeout: std::time::Duration,
    retries: u32,
    user_agent: String,
}

impl HttpClient {
    pub fn builder() -> HttpClientBuilder {
        HttpClientBuilder {
            timeout: std::time::Duration::from_secs(30),
            retries: 0,
            user_agent: "my-app/1.0".into(),
        }
    }
}

impl HttpClientBuilder {
    pub fn timeout(mut self, t: std::time::Duration) -> Self { self.timeout = t; self }
    pub fn retries(mut self, n: u32) -> Self { self.retries = n; self }
    pub fn user_agent(mut self, ua: impl Into<String>) -> Self { self.user_agent = ua.into(); self }
    pub fn build(self) -> HttpClient {
        HttpClient { timeout: self.timeout, retries: self.retries, user_agent: self.user_agent }
    }
}

let client = HttpClient::builder()
    .timeout(std::time::Duration::from_secs(5))
    .retries(3)
    .build();

// 10) Generic structs
struct Pair<T, U> { first: T, second: U }

impl<T: Clone, U: Clone> Pair<T, U> {
    fn duplicate(&self) -> (Pair<T, U>, Pair<T, U>) {
        (Pair { first: self.first.clone(), second: self.second.clone() },
         Pair { first: self.first.clone(), second: self.second.clone() })
    }
}

// 11) Lifetimes — struct holding a reference
struct Excerpt<'a> {
    part: &'a str,
}

impl<'a> Excerpt<'a> {
    fn announce(&self, prefix: &str) -> String {
        format!("{} {}", prefix, self.part)
    }
}

fn main_lifetime() {
    let s = String::from("the quick brown fox");
    let words: Vec<&str> = s.split_whitespace().collect();
    let e = Excerpt { part: words[0] };
    println!("{}", e.announce("first:"));
}

// 12) Default trait — for cheap default values
#[derive(Default, Debug)]
struct Config {
    host: String,
    port: u16,
    debug: bool,
}

let c = Config::default();   // String::new(), 0, false
let c2 = Config { port: 8080, ..Default::default() };

// 13) Serialisation — serde
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
struct Address {
    line1: String,
    city: String,
    country: String,
}

let a: Address = serde_json::from_str(r#"{\"line1\": \"1 Pitt St\", \"city\": \"Sydney\", \"country\": \"AU\"}"#).unwrap();
let json = serde_json::to_string(&a).unwrap();

// 14) Struct vs enum
// • struct — fixed shape; every value has all the same fields
// • enum   — choice between variants (Result, Option, your own DTOs)
// • If you find yourself with optional fields that only apply in some cases, an enum may fit better

// 15) Common bugs
// • Trying to mutate a borrowed struct → 'cannot borrow as mutable'; use &mut
// • Forgot 'pub' on a field → 'field is private' from outside the module
// • Derive Copy on a struct with String → won't compile; String is owned heap data
// • Lifetimes on every method param → most need only one; lifetime elision handles common cases
// • Public fields drift from invariants — prefer constructors that validate
// • Using struct update syntax with a non-Copy field → moves; can't use original after
// • Forgetting Hash + Eq when used as HashMap key — won't compile

Why it matters

Structs group fields; impl blocks add methods. Derive the common traits (Debug, Clone, PartialEq) you’ll inevitably need, keep fields private and expose builders or constructors that validate invariants, and use enums when a single shape can’t express “variant A or variant B” cleanly.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
struct User { name: String, age: u32 }

impl User {
    fn new(name: &str, age: u32) -> Self {
        Self { name: name.into(), age }
    }
}
Try it Yourself »

Discussion

Loading…