Debug School

rakesh kumar
rakesh kumar

Posted on

Difference between super and self in Rust

Rust (self and super)
Java (this and super)
Difference between self vs &self vs &mut self
Different way to use self in rust
When SHOULD you use self (rule of thumb

Rust (self and super)

self refers to current module

super refers to parent module

Rust (Struct + self)

self = current object

&self = read-only borrow

&mut self = mutable borrow

Self (capital S) = current type (like class name)

mod a {
    pub fn hello() { println!("a::hello"); }

    pub mod b {
        pub fn call_parent() {
            super::hello();      // parent module a
            self::inside_b();     // current module b
        }

        fn inside_b() {
            println!("b::inside_b");
        }
    }
}

fn main() {
    a::b::call_parent();
}
Enter fullscreen mode Exit fullscreen mode

Output

a::hello
b::inside_b
Enter fullscreen mode Exit fullscreen mode

Java (this and super)

this refers to current object

super refers to parent class

class Parent {
    void hello() { System.out.println("Parent.hello"); }
}

class Child extends Parent {
    void callParent() {
        super.hello();            // parent class method
        System.out.println(this.getClass().getSimpleName());
    }
}

public class Main {
    public static void main(String[] args) {
        Child c = new Child();
        c.callParent();
    }
}
Enter fullscreen mode Exit fullscreen mode

Output


Parent.hello
Child
Enter fullscreen mode Exit fullscreen mode

Difference between self vs &self vs &mut self

self vs &self vs &mut self (meaning)

self = current object by value (ownership move). Method call ke baad object usually usable nahi रहता (unless Copy).

&self = current object ka read-only borrow. Safe read, no change, object usable rehta hai.

&mut self = current object ka mutable borrow. You can change fields, but borrow ke दौरान same object ka koi aur borrow allowed nahi (safety).

&self (read-only) — when & why

✅ Use when: you only want to read data / compute something / print, without changing object.

#[derive(Debug)]
struct Wallet {
    owner: String,
    balance: i32,
}

impl Wallet {
    fn show(&self) {
        println!("Owner: {}, Balance: {}", self.owner, self.balance);
    }

    fn is_rich(&self) -> bool {
        self.balance >= 1000
    }
}

fn main() {
    let w = Wallet { owner: "Ashwani".to_string(), balance: 1200 };
    w.show();
    println!("Rich? {}", w.is_rich());
    w.show(); // still usable
}
Enter fullscreen mode Exit fullscreen mode

Output


Owner: Ashwani, Balance: 1200
Rich? true
Owner: Ashwani, Balance: 1200
Enter fullscreen mode Exit fullscreen mode

Why: no mutation needed, so &self is simplest + allows multiple readers.

B) &mut self (mutable) — when & why

✅ Use when: object ki state update करनी हो (increment, set, push, etc.)

struct Counter {
    value: i32,
}

impl Counter {
    fn inc(&mut self) {
        self.value += 1;
        println!("Incremented to {}", self.value);
    }

    fn add(&mut self, n: i32) {
        self.value += n;
        println!("Added {}, now {}", n, self.value);
    }
}

fn main() {
    let mut c = Counter { value: 0 };
    c.inc();
    c.add(5);
    c.inc();
}

Enter fullscreen mode Exit fullscreen mode

Output


Incremented to 1
Added 5, now 6
Incremented to 7
Enter fullscreen mode Exit fullscreen mode

Why: mutation needs exclusive access, so Rust forces &mut self.

C) self (by value / move) — when & why

✅ Use when: method should consume the object (ownership take), often:

builder-style chaining

converting to another type

“close / finalize” type behavior

return a new modified object (without mutable borrow)

#[derive(Debug)]
struct User {
    name: String,
    active: bool,
}

impl User {
    fn deactivate(self) -> Self {
        // consumes self, returns new Self
        Self { active: false, ..self }
    }
}

fn main() {
    let u1 = User { name: "Ashwani".to_string(), active: true };
    let u2 = u1.deactivate();

    println!("u2 = {:?}", u2);

    // println!("{:?}", u1); // ❌ u1 moved (not usable)
}
Enter fullscreen mode Exit fullscreen mode

Output

u2 = User { name: "Ashwani", active: false }

Why: you want to ensure old object cannot be used anymore (safety + intent).

Quick “when to use” cheat sheet

&self → read-only methods: show(), len(), to_string_view(), is_valid()

&mut self → state change methods: push(), set_*(), inc(), update()

self → consume/finish/transform: build(), into_*(), close(), deactivate(self)->Self
Enter fullscreen mode Exit fullscreen mode

Bonus: same method name, different receivers (common pattern)

Builder pattern:

#[derive(Debug)]
struct Request {
    url: String,
    timeout: u64,
}

impl Request {
    fn new(url: &str) -> Self {
        Self { url: url.to_string(), timeout: 30 }
    }

    fn timeout(mut self, t: u64) -> Self {
        // takes self, modifies locally, returns self (chainable)
        self.timeout = t;
        self
    }

    fn send(&self) {
        println!("Sending to {} with timeout {}", self.url, self.timeout);
    }
}

fn main() {
    let r = Request::new("https://api.example.com").timeout(10);
    r.send();
}
Enter fullscreen mode Exit fullscreen mode

Output

Sending to https://api.example.com with timeout 10

Different way to use self in rust

Basic self (object is consumed)
Rust code

struct File {
    name: String,
}

impl File {
    fn close(self) {
        println!("File {} closed", self.name);
    }
}

fn main() {
    let f = File { name: "data.txt".to_string() };
    f.close();

    // f.close(); ❌ not allowed, f is moved
}
Enter fullscreen mode Exit fullscreen mode

Output
File data.txt closed

Why use self here?

✔ After closing a file, it should not be used again
✔ Rust enforces this at compile time

Example 2️⃣ self returning Self (consume & return)
Rust code

#[derive(Debug)]
struct User {
    name: String,
    active: bool,
}

impl User {
    fn deactivate(self) -> Self {
        Self {
            active: false,
            ..self
        }
    }
}

fn main() {
    let u1 = User {
        name: "Ashwani".to_string(),
        active: true,
    };

    let u2 = u1.deactivate();
    println!("{:?}", u2);

    // println!("{:?}", u1); ❌ u1 moved
}
Enter fullscreen mode Exit fullscreen mode

Output

User { name: "Ashwani", active: false }
Enter fullscreen mode Exit fullscreen mode

Why use self?

✔ You want to transform the object
✔ Old version should not exist anymore

Example 3️⃣ Builder / chaining style (self)
Rust code

#[derive(Debug)]
struct Request {
    url: String,
    timeout: u64,
}

impl Request {
    fn new(url: &str) -> Self {
        Self { url: url.to_string(), timeout: 30 }
    }

    fn timeout(self, t: u64) -> Self {
        Self { timeout: t, ..self }
    }
}

fn main() {
    let r = Request::new("https://api.example.com")
        .timeout(10);

    println!("{:?}", r);
}
Enter fullscreen mode Exit fullscreen mode

Output
Request { url: "https://api.example.com", timeout: 10 }

Why use self here?

✔ Enables method chaining
✔ Avoids mutable references
✔ Very common in Rust APIs

Example 4️⃣ self with enums (consume variant)
Rust code

enum Payment {
    Cash,
    Card(String),
}

impl Payment {
    fn process(self) {
        match self {
            Payment::Cash => println!("Paid by cash"),
            Payment::Card(num) => println!("Paid by card {}", num),
        }
    }
}

fn main() {
    let p = Payment::Card("1234".to_string());
    p.process();

    // p.process(); ❌ moved
}
Enter fullscreen mode Exit fullscreen mode

Output
Paid by card 1234

Why use self?

✔ Payment should be used once
✔ Prevents double processing

Example 5️⃣ self inside trait method
Rust code

trait Shutdown {
    fn shutdown(self);
}

struct Server {
    id: u32,
}

impl Shutdown for Server {
    fn shutdown(self) {
        println!("Server {} stopped", self.id);
    }
}

fn main() {
    let s = Server { id: 1 };
    s.shutdown();

    // s.shutdown(); ❌ cannot reuse
}

Enter fullscreen mode Exit fullscreen mode

Output

Server 1 stopped
Enter fullscreen mode Exit fullscreen mode

Why use self?

✔ Trait enforces one-time action
✔ Safe resource handling

When SHOULD you use self (rule of thumb)

Use self when:

Object must be consumed

One-time action (close, shutdown, process)

Builder / chaining pattern

Transform object into another object

Prevent reuse bugs
Enter fullscreen mode Exit fullscreen mode
self in Rust means the method takes ownership of the current object, ensuring it cannot be used again after the call.
Enter fullscreen mode Exit fullscreen mode

Top comments (0)