← index

Sometimes read Rust like a Chinese Character

LearningRust · 2026-07-05

Some Context

I've chosen Rust as my second language after spending a couple of years with Python as my primary, nearly the last two (till date) of which has required me to use Python in soon-to-be production software. I've now experienced firsthand a lot of the "issues" commonly debated in online software communities regarding Python's limitations. Having felt these friction points in real-world contexts, I wouldn't call them "language issues" so much as non-ideal use cases. It's like using a flathead screwdriver to twist in a star-head screw: you can get it done, but you won't enjoy it, and it won't feel natural. I wouldn't call that a limitation of the screwdriver itself. I still love the tool, and I still love Python very much.

But as much as I love the Python programming language, there are just some things my darling can't handle or teach me. There are now parts of my codebase that I know will probably hold up under adversarial conditions, but I just don't enjoy reading them anymore.

Why Rust? Fuck if I know. It's a compiled, memory-safe systems language, so it's pretty fast as a consequence. It's also relatively young, meaning there's no cruft from legacy language designs and it's learnt their lessons. Honestly, it had me at "compiled, memory-safe systems language." There's really no need to overthink picking a second language as long as the secondary language has philosophies as directly in opposition to the primary language as possible and it's production capable in contexts beyond the capabilities of your primary language. That's all anyone needs to make a choice... unless your employer says otherwise, I guess.

The Moonlanding Encounter

Now, obviously, when you're learning a second language, you'd want to learn it in reference to your first, slowly carving out the similarities and differences between the two. And so, rather than starting with the canonical Rust Book, I set out to find a quick A-Z Python-to-Rust resource. I wanted to build an initial intuition about the language before I did a proper deep-dive.

I found this Microsoft resource, and as I was studying, I came across my first major "wtf" on my crossover from Python to Rust. Consider this:

if let Some(x) = optional_value {
    println!("{}", x);
}

You see, whenever I read a piece of code, I try to intuit the intent... And a pretty solid assumption I have about programming languages is that they're structurally designed to be read from left to right. Therefore, every keyword and variable (let's call them "terms") in a single statement, should technically be in a logically 'complete' state relative to the previous terms until the end of that statement, typically delimited by a semicolon or the start of a block (like a curly bracket {...} in Rust or a newline indentation in Python).

At least, that’s what I thought, coming from Python as my primary language.

Reading the statement above, I kept mentally running into a wall trying to figure out what Some(x) meant relative to the conditional assignment if let that preceded it. This was my "moonlanding" encounter with Rust, and I was diving in with a lot of prior assumptions. Lucky me, I read the warning label, 'RUST IS DIFFERENT!'

At first, I wondered if it was a function call... But how could you have a function call (or any kinda evaluation infact) on the left side of an assignment statement? That is quite literally a mathematical violation of the concept of assignment. Looking backwards from Some(x), it didn't make sense, and tacking on = optional_value {...} didn't help either. What was the evaluation flow here?

Ohkay, ohkay... Let's start with what we think it should be. So, if we ignore the problematic Some() on the left side and just keep if let x = optional_value {...}, it suddenly makes sense. It seems to say: "Let x be assigned the value of optional_value," and then, "if x (is truthy), execute the block."

In Python, combining assignment and conditional evaluation in a single line is pretty normal, thanks to a certain tusked operator we'll discuss later in this article. And we often do "truthy" or "falsy" evaluations on variables:

# Python's approach to truthy evaluation
user_id = get_cached_id()
if user_id:
    print(f"User logged in: {user_id}")

Now we have a theory of what that first line in Rust was trying to do... an assignment statement of some sort. But the statement isn't if let x = optional_value. We still had to figure out what Some(x) meant and what the heck it was doing on the left side of an assignment operator.


Detour: What is Some()?

I found that Some() isn’t a keyword or a function, regardless of the parentheses syntax. It’s a variant (or member) of a built-in Rust enum called Option.

// Here's the Option enum
enum Option<T> {
    Some(T),
    None,
}

Explainer: An enum (enumeration) is a data type that lets you give names to a small, fixed set of possible values that all belong to the same category. A variant (or member) is one of those possible named values. Furthermore, in Rust, an enum variant (or member) can optionally hold data... very unlike Python's enum which are basically symbolic constants of the same category unable to hold/carry data. Going further, the closest analogue to a Rust enum would conceptually be family of logically related Python dataclasses (plus pattern matching but forget about that for now). Take a look here:

// A Rust enum with two members: 
enum Shape {
    Circle { radius: f64 },
    Rectangle { width: f64, height: f64 },
    NoShape,
}

is roughly equivalent to:

from dataclasses import dataclass

# A couple of Python dataclasses
@dataclass
class Circle:
    radius: float

@dataclass
class Rectangle:
    width: float
    height: float

@dataclass
class NoShape:
    pass

That's not an exact 1-for-1 analogue, I'm intentional leaving out a thing because Rust's enum is not the central mystery right now. We'll circle back, I promise.

Ohkay, so the Option enum is Rust's built-in way to represent a value that might or might not exist. The None variant holds absolutely no data (representing the absence of a value), while the Some(T) variant wraps around any actual data type T.

At this point, I think it started to make some sense. Looking at if let Some(x) = optional_value, it seemed like Some(x) was checking the "value-ness" of x... after being assigned the value of optional_value? It does fit our earlier theory that the statement is checking for "truthiness" because it starts with an if.

Are we done? Is that it? I couldn't exactly let go... obviously. But I think I found the 'what is it doing?', now I needed to find the 'why... on Gawd's green earth is x being evaluated on the left side before optional_value has VISUALLY been assigned to it? I say 'visually' because I guess if you evaluated from left to right... conceptually, you'd want to write it like this:

// what my brain wanted it to look like:
let x = optional_value if Some(x) {
    println!("{}", x);
}

That looked right 'cause it sorta felt like the opposite of destructuring; especially since in the context of an enum variant (or member) that can hold data... an assignment must mean we're packing data into that member structure right?

Side Note: Destructuring is the process of breaking down a data structure into its constituent parts by matching its shape. In Python, we call it unpacking and we do it all the time with sequences:

# A python tuple
point = (10, 20)

# Classic destructuring (unpacking)
x, y = point  # The tuple shape is unpacked into x and y

At this point, I didn't think Some(x) was doing an evaluation, which sorta made it easier to accept that there's probably a logical reason as to why Rust is going against the 'popular-to-me' left-to-right evaluation flow. I've still got no idea but this line of thinking reminded me of our powerful Python tusked operator introduced in Python 3.8: the walrus operator (:=).

# The Python walrus operator
if (name := input("Name: ")):
    print(f"Hello, {name}")

# This is structurally equivalent to:
name = input("Name: ")
if name: 
    print(f"Hello, {name}")

The walrus operator lets us assign a value to a variable as part of a larger expression, instead of requiring a separate, isolated assignment statement. Aha! That is exactly what if let is trying to achieve. Yet, we still haven’t solved the central mystery: Why is Some(x) sitting on the left side of the equals sign?


Syntactic Consistency and... shapes?

It turns out this is a deliberate language design choice, of course it is, and the Rust designers intentionally subverted strict left-to-right predictability in favor of a profound concept: Syntactic Consistency.

In Rust, the syntax you use to create a data structure matches the syntax you use to unpack it. Consider:

// Creating the data (Packing)
let points = (10, 20, 30);
let user = User { name: "Alice", age: 30 };

// Extracting the data (Unpacking via shape matching)
let (x, y, z) = points;
let User { name, age } = user;

Notice how, on extraction, the left sides, let (x, y, z) and let User { name, age }, have the exact same visual "shapes" as the data structures used to create them. Rust directly overlays the left-hand template on top of the right-hand data, and the inner values drop neatly into the new variables.

This is called Pattern Matching. The code you write to pull data out of a structure looks exactly like the code you used to put data in. Contrast this with how Python traditionally handles objects and dictionaries:

# Tuple and Dictionary creation
points = (10, 20, 30)
user = { "name": "Alice", "age": 30 }

# Unpacking the values
x, y, z = points        # Position-based unpacking works for tuples...
name = user["name"]     # ...but dictionaries require explicit key lookups
age = user["age"]

While Python handles positional unpacking for tuples perfectly, extracting data from maps or custom objects requires you to shift syntax entirely, switching to explicit string lookups or attribute access (user.name).

If we look at Some() through this geometric "mold and cast" logic, if let Some(x) = optional_value suddenly becomes quite logical. It seems to be a single line conditional destructuring operation.

In Python, the left-hand side of a standard assignment or walrus operator must be a simple assignment target (a variable name, an attribute, or a subscript). You could never do this:

from enum import Enum

class Color(Enum):
    RED = 1

# fails: Cannot use assignment expressions with attribute (and enums are immutable anyway)
if Color.RED := 5:
    pass 

# fails: Cannot assign to an expression
a + b = 10

# fails Cannot assign to a literal
43 = x

# fails: Cannot assign to a function
func() = 5

I think you should get the point. Python throws an error because it treats the left side as an active assignment target, but in Rust:

let optional_value = Some(5); 
let Some(x) = optional_value; // valid 
// (I know, I know but [ir]refutable patterns are out of scope for this article. Lets agree on this in principle) 

This works because let Some(x) = ... is not an expression executing a function or modifying a constant. The left-hand side is a pattern. Note that the type of optional_value is Option<T> and it can either be Some or None. Think of let in Rust not as an assignment operator, but as an assertion of shape. It says: "Assert that the data on the right-hand side matches the structural mold on the left-hand side. If it matches, bind the inner values(s) on the right to the variable(s) on the left."

Python too can mimic this exact capability with match/case statements:

# Python's Structural Pattern Matching
match optional_value:
    case SomeVariant(x): # Legal! 'case' expects a pattern mold, not an assignment target
        print(x)
    case NoneVariant():
        print("No value")

Notice that SomeVariant(x) is perfectly legal here because case treats it as a pattern, just like Rust's let. However, Python keeps this behavior strictly isolated inside the match block. It has no single-line assignment equivalent. You cannot write SomeVariant(x) = optional_value or if SomeVariant(x) := optional_value:. Rust, on the other hand, allows you to bring that pattern-matching power right into your everyday if statements.


Conclusion: Chinese Characters

Bringing it back to our original code:

if let Some(x) = optional_value {
    println!("{}", x);
}

What this is actually saying is: "If optional_value matches the structural mold of Some, strip away the Some wrapper, bind the inner value directly to a new variable named x, and run this block."

It performs two operations simultaneously:

  1. The Check: It verifies if the data is actually there (the None check).
  2. The Extraction: It safely extracts the raw inner value without making you manually call an unwrap function.

After staring at a lot of similar concepts in Rust like while let, let else, and of course if let, I've realized the secret to learning Rust readability is you have to sometimes NOT read Rust like an English sentence... parsing every keyword letter-by-letter, strictly from left to right (if... then let... then Some...) will eventually throw a wrench in your mental interpreter's wheel. Instead, you have to read certain statements like a Chinese character. In Chinese, you don't sound out a character left-to-right; the entire visual shape represents a single, unified concept.

If you can treat entire phrases in Rust as a single visual character, your brain stops trying to parse the grammar of if let Some(x) =... and simply recognizes the whole shape as a single symbol meaning "extract-data-if-present," the weirdness is slightly less so, and then it's kinda easy to look at even... if you remember that something 'evaluation-looking' on the wrong side might be some pattern matching shenanigans going on. I guess you get used to that

Bonus content: Rust Enum vs Python Dataclasses

Told ya I'd circle around. Earlier I said the closest analogue to a Rust enum is a family of logically related Python dataclasses plus pattern matching. Here is what I meant by that. Let's take a look at that example again:

// A Rust enum with two variants: 
enum Shape {
    Circle { radius: f64 },
    Rectangle { width: f64, height: f64},
    NoShape,
}

is roughly equivalent to:

from dataclasses import dataclass

# A couple of Python dataclasses
@dataclass
class Circle:
    radius: float

@dataclass
class Rectangle:
    width: float
    height: float

@dataclass
class NoShape:
    pass

And to completely equate a Rust enum to Python, we have to use Python's match-case syntax on our dataclass family:

shape = Circle(4.6)

match shape:
    case Circle(radius):
        ...
    case Rectangle(width, height):
        ...
    case NoShape():
        ...

Conceptually, this is very similar to how Rust operates. But there is a huge difference under the hood regarding types.

In Python, Circle, Rectangle and NoShape are completely different types (they are distinct classes). But in Rust, an enum variant (like Circle or Some()) is not a type. The type is the enum itself (Shape or Option). The variants are just different structural shapes that the single type is allowed to take.

Because of this, the Rust compiler handles them very differently. If you have a variable:

let x = Some(5);

The compiler infers that x is of type Option<i32>. It doesn't type x as Some(), because Some() is NOT a type! And because the compiler only knows x is an Option, it knows x could be Some(), but it could also be None.

This is exactly why you can't just write let Some(y) = x; as a standalone assignment.

In Rust, a standard let assignment must be irrefutable, meaning the pattern on the left is guaranteed to match the data on the right 100% of the time (like let (a, b) = (1, 2)). But trying to pull Some() out of an Option is a refutable pattern. It might fail! The data might be None:

// what if x is None?
fn foo(x: Option<i32>) {
    let Some(y) = x;
}

And that, kids, is why we need if let. It is Rust's way of forcing us to acknowledge that the pattern might not match, giving the program a safe path forward if the data happens to be the wrong shape. You've just gotta understand that the compiler sees the Enum (Option) rather than the variant (Some()).

SIDE NOTE: Irrefutable and refutable patterns aren't topics I'm confident enough to speak on yet. Maybe I'll write another article about it when I do encounter it formally or run up against it.

Pheww... and that's a wrap. I hope you enjoyed reading this as much as I enjoyed writing it. I am very excited to be learning Rust. I've barely gotten started, and it's already feeling pretty powerful. I'll keep writing about other interesting concepts as I encounter them. All mistakes are mine. See ya around!

Written to