Python classes bundle data and behavior together. Rust separates them, and that separation taught me to ask a question I'd been skipping for years.

Python classes

In Python, when you need to track state across multiple operations, you reach for a class:

class Tokenizer:
    def __init__(self, input: str):
        self.chars = list(input)
        self.position = 0

    def advance(self):
        ch = self.chars[self.position]
        self.position += 1
        return ch

    def peek(self):
        return self.chars[self.position]

Every method receives self and can read or mutate any field. The language doesn't distinguish between methods that observe state and methods that change it. That's convenient, but it hides something important.

Rust separates data from behavior

In Rust, you define data with struct and behavior with impl:

struct Tokenizer {
    chars: Vec<char>,
    position: usize,
}

impl Tokenizer {
    fn advance(&mut self) -> Option<char> {
        let ch = self.chars.get(self.position).copied();
        self.position += 1;
        ch
    }

    fn peek(&self) -> Option<char> {
        self.chars.get(self.position).copied() // char is Copy, returns a value not a reference
    }
}

Notice the difference in method signatures. advance takes &mut self because it modifies position. peek takes &self because it only reads. This isn't a style choice. The compiler enforces it.

What Rust is explicit about

In Python, I never asked "does this method mutate state?" I'd look at the implementation if I cared. In Rust, the signature tells you before you read a single line of the method body.

&self means: this method borrows the struct immutably. You can call it while other code holds a reference. It's safe to call in parallel (unlike Python, where you'd need manual locking to guarantee the same).

&mut self means: this method borrows the struct mutably. No other code can access the struct while this method runs. It's changing something.

Python doesn't model this distinction at all. You can write an observer method that quietly mutates state, and nothing stops you. In Rust, the signature is a contract: &self means you cannot mutate, full stop.

Why this matters

When I converted a function-based parser to use structs, I had to answer questions Python never made me ask:

  • Which methods need mutable access? Only advance. The position changes.
  • Which methods are pure observers? peek and is_at_end. They look but don't touch.
  • What data does the struct actually need? Just the input and position. Nothing else.

In Python, I might have added a self._current_token field to signal "internal only", but that's still at the class level, so any method can modify it indistinguishably. Rust's borrow checker would have forced me to think harder about whether that state was necessary.

What Rust structs give you is clarity. Data lives in one place. Behavior lives in another. And every method signature tells you exactly how it interacts with the data.

Python's flexibility is often what you want, but Rust takes explicit is better than implicit further, and it changes the questions you ask back in Python. Does this method need to mutate state, or am I just being lazy? Could this be a standalone function? Am I bundling state because it's necessary, or because self.x is easy to type?


Rust's struct + impl separation isn't about syntax. It's about forcing you to decide who owns data and who can change it. In our Rust cohort, this is often the first moment people think carefully about how data actually flows through a program. In Python, these decisions are often implicit. In Rust, they're in the signatures and the compiler enforces them.

That explicitness is uncomfortable at first. Then it becomes a habit. Then you start applying it even when the compiler isn't watching.