5 min read #rust #Type System
On this page

Structs and Traits

There are three kinds of structs (named fields, tuples, and units). A trait is Rust’s answer to "interfaces"—defining a set of method signatures that any type can implement. The derive macro automatically generates common traits (Clone/Debug/Serialize) for structs, while the orphan rule restricts "who can implement whose trait," preventing dependency conflicts.

Structs: More Than Just "Collections of Data"

struct Point { x: f64, y: f64 }      // named fields — the most common
struct Color(u8, u8, u8);            // tuple struct — anonymous fields, suitable for simple wrappers
struct AlwaysEqual;                   // unit struct — ZST, used as a marker type

Tuple structs are suitable for "newtype wrappers"—wrapping an underlying type with a single-field struct to give it new type semantics and prevent misuse:

struct Meters(f64);                  // Not f64 — it's Meters!
struct Feet(f64);

let m = Meters(100.0);
let f = Feet(328.0);
// let sum = m.0 + f.0;              // You can do this, but semantically you shouldn't compare directly
// The compiler won't stop you, but the type system helps distinguish "this is meters" from "that is feet"

Traits: Contracts for Behavior

A trait defines "a set of method signatures"—any type implementing that trait must provide these methods:

trait Summary {
    fn summarize(&self) -> String;    // Must be implemented (no default)
    
    fn default_summary(&self) -> String {  // Has a default implementation, optional to override
        String::from("(Read more...)")
    }
}

impl Summary for Point {
    fn summarize(&self) -> String {
        format!("Point({}, {})", self.x, self.y)
    }
}

Traits are similar to interfaces in Java/C#, but with an important distinction: you can implement your own traits for external types (but you cannot implement external traits for external types—the orphan rule).

Derive: Compiler-Generated Code

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct Point { x: f64, y: f64 }

derive requires that all fields also implement the corresponding trait. For example, #[derive(Clone)] requires every field to be Clone. The compiler checks these constraints and then generates templated impls—no macros or code generation tools needed.

Orphan Rule: Why You Can't Just impl Anything

// Your crate: my_lib
impl Display for MyType { ... }      // OK: MyType is yours
impl MyTrait for String { ... }      // OK: MyTrait is yours
impl Display for Vec<MyType> { ... } // OK: The parameter MyType of Vec is yours

// impl Display for String { ... }   // ERROR: Display(std) + String(std)
//     Both are defined in other crates → orphan rule prohibits this

This rule prevents two crates from providing conflicting impls for the same (type, trait) combination. If this were allowed, the compiler wouldn't know which one to choose, and the coherence check during the linking phase would fail. This is the core guarantee of Rust's modular trait system.

Trait Bounds: Constraining Generics

// Single bound — syntactic sugar
fn notify(item: &impl Summary) { }

// Equivalent generic syntax
fn notify<T: Summary>(item: &T) { }

// Multiple bounds
fn notify<T: Summary + Display>(item: &T) { }

// Where clause — more readable for complex bounds
fn complex<T, U>(t: &T, u: &U) -> T::Output
where
    T: Display + Clone,
    U: Clone + Debug,
    T: Add<U>,
{ ... }

References

  • Rust Book: Chapter 5 (struct), Chapter 10.2 (trait)
  • Rust Reference: Traits, orphan rules, derive

Keywords: struct, trait, impl, derive, orphan rule, coherence, trait bound, where clause