Skip to content

Rust for Pythonistas

gsjonio edited this page Jul 14, 2026 · 1 revision

Rust for Pythonistas

A study page for a Python developer reading the hightower codebase. It maps Rust's ideas onto what you already know, using real examples from this project. It is not a Rust tutorial -- it is a set of bridges.

The mental-model shifts

Ownership & borrowing -- there is no garbage collector

In Python, every value is reference-counted and the runtime frees it when the last name goes away. Rust has no GC. Instead, every value has exactly one owner, and it is freed when the owner goes out of scope.

You lend access instead of copying:

  • &T -- a shared, read-only borrow (many at once). Like passing an object you promise not to mutate.
  • &mut T -- an exclusive, writable borrow (only one at a time).
fn render_process_table(processes: &[ProcessInfo]) -> String

That & means "I am only borrowing the list to read it; I will not take it or change it." The caller keeps ownership. The compiler checks this at compile time, so a whole class of "who freed this / who mutated this" bugs cannot happen.

The nearest Python cousin is the convention "don't mutate the argument". Rust turns that convention into a compiler rule.

RAII -- freeing is tied to scope, like a with block

When an owner goes out of scope, its drop runs. hightower uses this to close Windows handles automatically:

struct SnapshotHandle(HANDLE);
impl Drop for SnapshotHandle {
    fn drop(&mut self) {
        // runs automatically at end of scope, on every path
        let _ = unsafe { CloseHandle(self.0) };
    }
}

This is exactly a with open(...) as f: context manager -- guaranteed cleanup -- except it is tied to the value's lifetime instead of an indented block.

Result<T, E> -- errors are values, not exceptions

Rust has no exceptions. A function that can fail returns Result<T, E>:

fn list(&self) -> Result<Vec<ProcessInfo>, HightowerError>;

The caller must handle both the Ok and the Err -- the compiler will not let you forget. The ? operator is the ergonomic shortcut: "if this is Err, return it; otherwise unwrap the Ok."

Python Rust
raise ValueError(...) return Err(HightowerError::...)
try/except match on the Result, or ?
a function might raise (invisible) the return type says it can fail

Option<T> -- no None surprises

Option<T> is Some(value) or None. It is Python's Optional[T], but you cannot accidentally use a None as if it were a value -- the compiler forces you to check.

executable_path: Option<PathBuf>,   // Some(path) or None (restricted process)

Traits -- like abc.ABC, checked at compile time

A trait is an abstract capability, very close to an abc.ABC with @abstractmethod:

pub trait ProcessLister {
    fn list(&self) -> Result<Vec<ProcessInfo>, HightowerError>;
}

The difference from Python's duck typing: the compiler verifies, before the program runs, that any type claiming to implement ProcessLister really has a list method with that exact signature. There is no "works until it doesn't at runtime".

Box<dyn ProcessLister> is "some value that implements ProcessLister, decided at runtime" -- that is Python-style dynamic dispatch, opted into explicitly.

Enums carry data -- like a tagged union / match on type

Rust enums are far richer than Python's enum.Enum; a variant can hold data:

enum SignatureStatus {
    Unchecked,
    Signed { publisher: Option<String> },
    Unsigned,
    Unknown,
}

You handle them with match, which the compiler checks for exhaustiveness -- add a variant and every match that forgot it fails to compile. The closest Python pattern is match/case on a class hierarchy, but without the exhaustiveness guarantee.

unsafe -- "I am upholding this invariant, not the compiler"

unsafe does not mean "wrong". It means "the compiler cannot verify this, so I promise to uphold the rules myself" -- typically when calling a raw C/OS API. hightower requires a // SAFETY: comment above every unsafe block. The nearest Python cousin is a ctypes FFI call: equally unchecked, just without a keyword forcing you to acknowledge it.

Tooling map

Python Rust Note
pip / venv cargo build, test, run, dependency manager, all in one
requirements.txt / pyproject.toml Cargo.toml declared deps
poetry.lock Cargo.lock pinned, reproducible versions
pytest cargo test tests live next to the code in #[cfg(test)] mod tests
black cargo fmt formatting
ruff / flake8 cargo clippy linting (hightower runs it with -D warnings)
mypy the compiler itself types are not optional
a package a crate hightower is a workspace of three crates

Where to look in the code

  • Ports (traits): core/src/ports.rs
  • The domain model & a derived-Ord enum: core/src/process.rs
  • Real unsafe + RAII guards: adapters/src/procinfo.rs
  • Composition root (dependency injection): cli/src/main.rs

Every public item in core carries a /// doc comment explaining the why, and many include a short Python bridge inline. Reading the crate top to bottom is a decent Rust-by-example tour.