Debug School

rakesh kumar
rakesh kumar

Posted on

Difference between enum,struct and trait in rust

What they are (simple)
struct — use when you have one “thing” with properties
enum — use when a value can be ONE of multiple forms
trait — use when multiple types share the same behavior
How Rust allows (multiple instances per enum variant)
How Rust’s enum + match replaces classic inheritance-based polymorphism
Rust enums are much more powerful than Java/Python enums

What they are (simple)

struct: holds data/fields (like a Java class used as a data model).

enum: holds one of many variants (like Java enum, but Rust enums can also store data per variant).

trait: defines shared behavior (like a Java interface).

1)

struct — use when you have one “thing” with properties

When to use (Rust + Java)

Use struct/class when:

You model a real object: User, Hospital, Car

It has fixed fields: id, name, price

You want methods on it


Rust code + output
#[derive(Debug)]
struct User {
    id: i32,
    name: String,
}

impl User {
    fn label(&self) -> String {
        format!("{} ({})", self.name, self.id)
    }
}

fn main() {
    let u = User { id: 1, name: "Ashwani".to_string() };
    println!("{:?}", u);
    println!("{}", u.label());
}

Enter fullscreen mode Exit fullscreen mode

Output

User { id: 1, name: "Ashwani" }
Ashwani (1)
Enter fullscreen mode Exit fullscreen mode

Java equivalent + output

class User {
    int id;
    String name;

    User(int id, String name) {
        this.id = id;
        this.name = name;
    }

    String label() {
        return name + " (" + id + ")";
    }

    @Override
    public String toString() {
        return "User{id=" + id + ", name='" + name + "'}";
    }
}

public class Main {
    public static void main(String[] args) {
        User u = new User(1, "Ashwani");
        System.out.println(u);
        System.out.println(u.label());
    }
}

Enter fullscreen mode Exit fullscreen mode

Output

User{id=1, name='Ashwani'}
Ashwani (1)
Enter fullscreen mode Exit fullscreen mode

2)

enum — use when a value can be ONE of multiple forms

When to use

Use enum when:

A variable can be different “shapes”: Success/Failure, Card/UPI/Cash

You want compile-time safety (no invalid state)

You want data with variants (Rust is very strong here)
Enter fullscreen mode Exit fullscreen mode

Rust code + output (enum with data + match)

[derive(Debug)]

enum Payment {
    Cash,
    Card { last4: String },
    Upi(String),
}

fn main() {
    let p1 = Payment::Cash;
    let p2 = Payment::Card { last4: "1234".to_string() };
    let p3 = Payment::Upi("ashwani@upi".to_string());

    for p in [p1, p2, p3] {
        match p {
            Payment::Cash => println!("Pay by Cash"),
            Payment::Card { last4 } => println!("Pay by Card ending {}", last4),
            Payment::Upi(id) => println!("Pay by UPI {}", id),
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

Output

Pay by Cash
Pay by Card ending 1234
Pay by UPI ashwani@upi
Enter fullscreen mode Exit fullscreen mode

Java equivalent + output (closest simple version)

Java enum can store fields, but it’s not as flexible as Rust’s “variants with different shapes”.

enum PaymentType { CASH, CARD, UPI }

public class Main {
    public static void main(String[] args) {
        PaymentType p1 = PaymentType.CASH;
        PaymentType p2 = PaymentType.CARD;
        PaymentType p3 = PaymentType.UPI;

        System.out.println("Pay by " + p1);
        System.out.println("Pay by " + p2 + " ending 1234");
        System.out.println("Pay by " + p3 + " ashwani@upi");
    }
}

Enter fullscreen mode Exit fullscreen mode

Output

Pay by CASH
Pay by CARD ending 1234
Pay by UPI ashwani@upi
Enter fullscreen mode Exit fullscreen mode

In Java, to match Rust’s enum power, you usually use sealed classes (Java 17+) or class hierarchies.

3)

trait — use when multiple types share the same behavior

When to use

Use trait/interface when:

Many types do the same action: pay(), draw(), export()

You want polymorphism without inheritance

You want generic code: “anything that can print a summary”

Rust code + output (trait + two structs)

trait Summary {
    fn summary(&self) -> String;
}

struct Doctor {
    name: String,
}

struct Hospital {
    name: String,
}

impl Summary for Doctor {
    fn summary(&self) -> String {
        format!("Doctor: {}", self.name)
    }
}

impl Summary for Hospital {
    fn summary(&self) -> String {
        format!("Hospital: {}", self.name)
    }
}

fn print_summary(item: &impl Summary) {
    println!("{}", item.summary());
}

fn main() {
    let d = Doctor { name: "Dr. Mehta".to_string() };
    let h = Hospital { name: "City Care".to_string() };

    print_summary(&d);
    print_summary(&h);
}


Enter fullscreen mode Exit fullscreen mode

Output

Doctor: Dr. Mehta
Hospital: City Care
Enter fullscreen mode Exit fullscreen mode

Java equivalent + output (interface)

interface Summary {
    String summary();
}

class Doctor implements Summary {
    String name;
    Doctor(String name) { this.name = name; }
    public String summary() { return "Doctor: " + name; }
}

class Hospital implements Summary {
    String name;
    Hospital(String name) { this.name = name; }
    public String summary() { return "Hospital: " + name; }
}

public class Main {
    static void printSummary(Summary item) {
        System.out.println(item.summary());
    }

    public static void main(String[] args) {
        Doctor d = new Doctor("Dr. Mehta");
        Hospital h = new Hospital("City Care");
        printSummary(d);
        printSummary(h);
    }
}
Enter fullscreen mode Exit fullscreen mode

Output

Doctor: Dr. Mehta
Hospital: City Care
Enter fullscreen mode Exit fullscreen mode

Quick comparison table

How Rust allows (multiple instances per enum variant)

enum Payment {
    Cash,
    Card { last4: String },
    Upi(String),
}

fn main() {
    let payments = vec![
        Payment::Cash,
        Payment::Card { last4: "1234".to_string() },
        Payment::Card { last4: "5678".to_string() }, // another Card
        Payment::Upi("ashwani@upi".to_string()),
        Payment::Card { last4: "9999".to_string() }, // another Card
    ];

    for p in payments {
        match p {
            Payment::Cash => println!("Pay by Cash"),
            Payment::Card { last4 } => println!("Pay by Card ending {}", last4),
            Payment::Upi(id) => println!("Pay by UPI {}", id),
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Output

Pay by Cash
Pay by Card ending 1234
Pay by Card ending 5678
Pay by UPI ashwani@upi
Pay by Card ending 9999
Enter fullscreen mode Exit fullscreen mode

🧠 Why this works (important concept)

Payment::Card { last4: "1234".to_string() }
Payment::Card { last4: "5678".to_string() }
Enter fullscreen mode Exit fullscreen mode

These are two different values, just like:

Circle { r: 2 }
Circle { r: 5 }
Another Example

enum Payment {
    Card { last4: String },
}

fn main() {
    let p1 = Payment::Card { last4: "1234".to_string() };
    let p2 = Payment::Card { last4: "5678".to_string() };
    let p3 = Payment::Card { last4: "9999".to_string() };

    for p in [p1, p2, p3] {
        match p {
            Payment::Card { last4 } => println!("Card ending {}", last4),
        }

Enter fullscreen mode Exit fullscreen mode

}
}
Output

Card ending 1234
Card ending 5678
Card ending 9999
Enter fullscreen mode Exit fullscreen mode

Java enum = SINGLETONS (this is the key reason)

how Rust’s enum + match replaces classic inheritance-based polymorphism

1️⃣ Polymorphism (what it really means)

Polymorphism = one interface, many behaviors
Enter fullscreen mode Exit fullscreen mode

“Different types respond to the same operation in their own way.”

Polymorphism using Inheritance (Java / Python style)

Java (Inheritance + Method Override)
Enter fullscreen mode Exit fullscreen mode
abstract class Shape {
    abstract double area();
}

class Circle extends Shape {
    double r;
    Circle(double r) { this.r = r; }

    double area() {
        return Math.PI * r * r;
    }
}

class Rectangle extends Shape {
    double w, h;
    Rectangle(double w, double h) {
        this.w = w; this.h = h;
    }

    double area() {
        return w * h;
    }
}

public class Main {
    public static void main(String[] args) {
        Shape s1 = new Circle(2);
        Shape s2 = new Rectangle(3, 4);

        System.out.println(s1.area());
        System.out.println(s2.area());
    }
}

Enter fullscreen mode Exit fullscreen mode

Output

12.566370614359172
12.0
Enter fullscreen mode Exit fullscreen mode

✔ Runtime polymorphism
❌ Inheritance hierarchy
❌ Possible invalid states
❌ Runtime dispatch cost

Python (Inheritance)

class Shape:
    def area(self):
        raise NotImplementedError

class Circle(Shape):
    def __init__(self, r):
        self.r = r

    def area(self):
        return 3.14 * self.r * self.r

class Rectangle(Shape):
    def __init__(self, w, h):
        self.w = w
        self.h = h

    def area(self):
        return self.w * self.h

shapes = [Circle(2), Rectangle(3, 4)]

for s in shapes:
    print(s.area())
Enter fullscreen mode Exit fullscreen mode

Output

12.56
12
Enter fullscreen mode Exit fullscreen mode
✔ Very flexible
❌ No compile-time safety
❌ Errors only at runtime
Enter fullscreen mode Exit fullscreen mode

2️⃣ Polymorphism in Rust using Traits (behavior-based)

Rust avoids inheritance.

Instead → trait = behavior contract
Enter fullscreen mode Exit fullscreen mode
trait Shape {
    fn area(&self) -> f64;
}

struct Circle {
    r: f64,
}

struct Rectangle {
    w: f64,
    h: f64,
}

impl Shape for Circle {
    fn area(&self) -> f64 {
        3.14 * self.r * self.r
    }
}

impl Shape for Rectangle {
    fn area(&self) -> f64 {
        self.w * self.h
    }
}

fn print_area(s: &dyn Shape) {
    println!("{}", s.area());
}

fn main() {
    let c = Circle { r: 2.0 };
    let r = Rectangle { w: 3.0, h: 4.0 };

    print_area(&c);
    print_area(&r);
}
Enter fullscreen mode Exit fullscreen mode

Output

12.56
12
Enter fullscreen mode Exit fullscreen mode
✔ Safe polymorphism
✔ No inheritance
✔ Explicit behavior
Enter fullscreen mode Exit fullscreen mode

Rust’s enum + match (NO inheritance, NO traits)

This is the most important Rust concept.

Rust says:
“Instead of many classes, use one enum with variants.”

Polymorphism using enum + match (Rust-native)

enum Shape {
    Circle { r: f64 },
    Rectangle { w: f64, h: f64 },
}

fn area(s: &Shape) -> f64 {
    match s {
        Shape::Circle { r } => 3.14 * r * r,
        Shape::Rectangle { w, h } => w * h,
    }
}

fn main() {
    let s1 = Shape::Circle { r: 2.0 };
    let s2 = Shape::Rectangle { w: 3.0, h: 4.0 };

    println!("{}", area(&s1));
    println!("{}", area(&s2));
}
Enter fullscreen mode Exit fullscreen mode

Output

12.56
12
Enter fullscreen mode Exit fullscreen mode

✔ Compile-time exhaustive checking
✔ No inheritance tree
✔ No runtime dispatch
✔ Faster & safer
Enter fullscreen mode Exit fullscreen mode

🔥 Why enum + match is special
Java / Python problem

You can forget to override a method

You can add a subclass and forget to handle it

Errors appear at runtime

Rust solution

enum Status { Open, Closed }

match status {
    Status::Open => println!("Open"),
    Status::Closed => println!("Closed"),
}
Enter fullscreen mode Exit fullscreen mode

If you add:

Status::Pending

❌ Compiler ERROR until you handle it

Rust enums are much more powerful than Java/Python enums

Java / Python enum (simple labels)

enum Status { SUCCESS, FAILURE }
Enter fullscreen mode Exit fullscreen mode
from enum import Enum
class Status(Enum):
    SUCCESS = 1
    FAILURE = 2
Enter fullscreen mode Exit fullscreen mode

✔ Just named constants
❌ No attached data per variant
❌ Limited behavior

Rust enum (variants can hold data)

enum Result {
    Ok(i32),
    Err(String),
}
Enter fullscreen mode Exit fullscreen mode

✔ Each variant can store different data
✔ Variants can have different shapes
✔ Checked at compile time

This single feature alone explains most of Rust’s enum usage.

2️⃣ Enums replace null (BIG reason)
Java / Python

String name = null;   // runtime NPE risk

name = None           # runtime error risk
Enter fullscreen mode Exit fullscreen mode

Rust (no null)

enum Option<T> {
    Some(T),
    None,
}

Enter fullscreen mode Exit fullscreen mode

Usage:

let name: Option<String> = Some("Ashwani".to_string());

match name {
    Some(n) => println!("{}", n),
    None => println!("No name"),
}

Enter fullscreen mode Exit fullscreen mode

✔ Forces you to handle “missing value”
✔ No NullPointerException
✔ Compiler enforces safety

👉 This alone causes enums to appear everywhere

3️⃣ Enums replace exceptions (error handling)
Java

try {
    readFile();
} catch (IOException e) {
    // runtime path
}
Enter fullscreen mode Exit fullscreen mode

Python

try:
    read_file()
except IOError:
    pass

Enter fullscreen mode Exit fullscreen mode

✔ Flexible
❌ Errors can be ignored
❌ Hidden control flow

Rust

enum Result<T, E> {
    Ok(T),
    Err(E),
}

fn read_file() -> Result<String, String> {
    Ok("file content".to_string())
}
Enter fullscreen mode Exit fullscreen mode

✔ Errors are values
✔ Must be handled explicitly
✔ No hidden jumps

👉 That’s why you see Result everywhere

4️⃣ Enums replace inheritance & polymorphic class hierarchies
Java style
abstract class Shape {}
class Circle extends Shape {}
class Rectangle extends Shape {}

Rust style

enum Shape {
    Circle { r: f64 },
    Rectangle { w: f64, h: f64 },
}
Enter fullscreen mode Exit fullscreen mode

Then:

match shape {
    Shape::Circle { r } => ...
    Shape::Rectangle { w, h } => ...
}
Enter fullscreen mode Exit fullscreen mode

✔ No runtime dispatch
✔ Faster
✔ Exhaustive checking (no missing case)

5️⃣ Enums replace dynamic typing (Python-like behavior)
Python

data = [1, "hello", 3.5, True]
Enter fullscreen mode Exit fullscreen mode

Rust

enum Value {
    Int(i32),
    Text(String),
    Float(f64),
    Bool(bool),
}

let data = vec![
    Value::Int(1),
    Value::Text("hello".to_string()),
    Value::Float(3.5),
    Value::Bool(true),
];
Enter fullscreen mode Exit fullscreen mode
✔ Python flexibility
✔ Rust safety
✔ No runtime type errors
Enter fullscreen mode Exit fullscreen mode

6️⃣ match + enum = compiler-enforced correctness

Rust forces exhaustive handling:

match status {
    Status::Open => ...
    Status::Closed => ...
}
Enter fullscreen mode Exit fullscreen mode

If you add:

Status::Pending

👉 Compiler error until you handle it

Java / Python:
❌ No such protection

7️⃣ Why Java & Python don’t use enums this way

Top comments (0)