-
Notifications
You must be signed in to change notification settings - Fork 0
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.
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]) -> StringThat & 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.
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.
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> 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)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.
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 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.
| 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 |
- Ports (traits):
core/src/ports.rs - The domain model & a derived-
Ordenum: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.