Rust untuk Developer Python: Panduan Praktis
Kalau kamu sudah bisa Python, sebenarnya kamu sudah mengenal banyak ide Rust. Rust punya perulangan for atas iterator, tuple, closure (mirip lambda di Python, tapi lebih berguna), match yang bekerja seperti match di Python, dan rantai iterator yang mengerjakan tugas list comprehension. Rust bahkan meminjam sedikit selera Python: satu cara yang jelas untuk memformat kode, tool package yang ramah, dan pesan error yang berusaha keras membantumu.
Yang baru sebagian besar menyangkut compiler. Python mengecek tipe saat programmu berjalan; Rust mengecek semuanya sebelum program berjalan. Python membersihkan memori dengan garbage collector; Rust memakai ownership (kepemilikan) dan borrowing (peminjaman), yang dicek saat compile. Python punya None dan exception; Rust punya Option dan Result. Tiga hal inilah yang akan menyita sebagian besar waktu belajarmu.
Lalu, kenapa developer Python belajar Rust? Alasan jujurnya kira-kira begini:
- Kecepatan. Rust di-compile menjadi kode mesin native. Pekerjaan berat di CPU yang lambat di Python murni (parsing, hitung-hitungan angka, perulangan besar) biasanya jauh lebih cepat di Rust, tanpa trik tuning apa pun.
- Thread sungguhan. Di CPython standar, GIL hanya mengizinkan satu thread menjalankan kode Python pada satu waktu. Thread Rust berjalan di semua core CPU-mu sekaligus, dan compiler menghentikan data race sebelum programmu berjalan.
- Bug ketahuan lebih awal. Salah ketik nama atribut,
Noneyang terlupa, atau fungsi yang dipanggil dengan tipe yang salah: di Python kamu baru menemukannya saat baris itu dijalankan. Di Rust, program tidak mau di-compile sampai semuanya diperbaiki. - Kamu tidak harus memilih. Dengan PyO3 dan maturin, kamu bisa menulis bagian yang lambat dari proyek Python dengan Rust, lalu meng-
import-nya dari Python seperti modul biasa. Tool Python populer seperti Polars, Ruff, dan uv ditulis dengan Rust.
Kalau kamu mencari perbandingan yang lebih umum antara Rust dengan Python, Go, dan C++, lihat Rust vs C++, Go, dan Python.
Konsep bahasa: dari Python ke Rust
Section titled “Konsep bahasa: dari Python ke Rust”Pakai tabel ini seperti kamus percakapan. Kolom terakhir berisi link ke pelajaran di situs ini yang menjelaskan sisi Rust-nya.
| Python | Rust | Pelajari lebih lanjut |
|---|---|---|
| Tipe dinamis, dicek saat runtime | Tipe statis, dicek saat compile. Rust menebak (infer) sebagian besar tipe, jadi kamu menulis tipe lebih sedikit daripada yang kamu kira | Tipe data |
Type hint (def f(x: int) -> str) |
Tipe di signature fungsi wajib ditulis, dan compiler menegakkannya | Fungsi |
x = 5 |
let x = 5; (tidak bisa diubah). let mut x = 5; kalau kamu ingin mengubahnya |
Variabel |
int (bisa membesar sebesar apa pun) |
Integer berukuran tetap: i32, i64, u64, usize, i128… Bisa overflow |
Tipe data, Integer overflow |
float |
f64 (atau f32) |
Tipe data |
str |
String (dimiliki, bisa bertambah panjang) dan &str (tampilan teks yang dipinjam) |
String |
f-string: f"Hi {name}" |
format!("Hi {name}") dan println!("Hi {name}") |
Output |
print(repr(x)) / __repr__ |
println!("{x:?}") dengan #[derive(Debug)] |
Output, Trait yang sering dipakai |
__str__ |
Implementasikan Display (lalu .to_string() juga langsung bisa dipakai) |
Trait yang sering dipakai |
__eq__, __hash__, __lt__ |
Derive PartialEq, Eq, Hash, PartialOrd, Ord |
Trait yang sering dipakai |
None, Optional[T] |
Option<T>: bisa Some(value) atau None |
Option |
x or default |
x.unwrap_or(default) |
Option |
raise, try/except |
Result<T, E> + operator ? |
Result, Error kustom |
Exception untuk bug sungguhan (assert, IndexError) |
panic! (menghentikan thread) |
Panic |
class dengan __init__ dan method |
struct untuk datanya + blok impl untuk method-nya; new hanyalah fungsi biasa |
Struct |
@dataclass |
struct dengan #[derive(Debug, Clone, PartialEq)] |
Struct |
self |
&self (membaca), &mut self (mengubah), atau self (mengambil ownership) |
Struct |
| Inheritance | Tidak ada inheritance. Pakai komposisi (struct di dalam struct) + trait dengan method default | Trait, Design pattern |
Duck typing, Protocol, ABC |
trait |
Trait |
| List berisi objek dari class yang berbeda-beda | Vec<Box<dyn Trait>> (sebuah trait object) |
Trait object |
TypeVar / fungsi generik |
Generics dengan trait bound: fn largest<T: Ord>(items: &[T]) |
Generik |
Class Enum |
enum yang setiap varian-nya bisa membawa datanya sendiri |
Enum |
match / case (3.10+) |
match (compiler mengecek bahwa semua kasus sudah kamu tangani) |
Match, Pattern matching |
if my_list: (truthiness) |
Tulis secara eksplisit: if !my_list.is_empty(). Hanya bool yang boleh masuk ke if |
If…Else |
for i in range(10) |
for i in 0..10 |
Perulangan for |
enumerate(xs), zip(a, b) |
xs.iter().enumerate(), a.iter().zip(b.iter()) |
Iterator |
lambda x: x + 1 |
Closure: |x| x + 1 (dan boleh lebih dari satu baris) |
Closure |
List comprehension, map, filter |
Iterator adapter: filter, map, lalu collect() |
Iterator |
Generator dan yield |
Tipe buatanmu sendiri yang mengimplementasikan Iterator, atau std::iter::from_fn |
Iterator kustom |
with open(...) as f: |
Tidak perlu menulis apa-apa: file dan lock tertutup sendiri saat pemiliknya keluar dari scope (Drop) |
Scope, Membaca dan menulis file |
Argumen default, *args, **kwargs |
Tidak didukung. Pakai parameter Option, slice (&[T]), struct dengan Default, atau builder |
Fungsi, Design pattern |
| Decorator | Tidak ada padanan langsung. Bungkus dengan closure, atau pakai attribute macro (seperti #[tokio::main]) |
Closure, Makro prosedural |
import dan modul |
mod dan use |
Modul |
| Package di PyPI | Crate di crates.io | Cargo dan crate |
if __name__ == "__main__": |
fn main() |
Mulai dengan Rust |
threading (dengan GIL) |
std::thread: thread paralel sungguhan |
Konkurensi |
multiprocessing, concurrent.futures |
Thread, atau crate rayon untuk iterator paralel |
Konkurensi, Shared state |
asyncio, async def, await |
async fn dan .await, dengan runtime seperti tokio |
Async/Await |
7 // 2, 2 ** 10 |
7 / 2 (pembagian integer kalau keduanya integer), 2_i64.pow(10) |
Operator |
int("42"), str(42) |
"42".parse::<i32>() (mengembalikan Result), 42.to_string() |
Konversi tipe, Konversi |
Koleksi
Section titled “Koleksi”Semua koleksi bawaan Python punya tempat di standard library Rust. Perbedaan terbesarnya: koleksi Rust hanya menampung satu tipe (Vec<i32> hanya berisi i32), dan HashMap tidak mengingat urutan penyisipan.
| Python | Rust | Pelajari lebih lanjut |
|---|---|---|
list |
Vec<T> |
Vektor |
tuple |
Tuple (i32, String), atau struct kecil dengan field bernama |
Tuple |
dict |
HashMap<K, V> (urutannya tidak tetap!) |
HashMap |
dict yang key-nya perlu terurut |
BTreeMap<K, V> |
HashMap |
dict yang perlu mengingat urutan penyisipan |
Crate indexmap |
|
set, frozenset |
HashSet<T>, BTreeSet<T> |
HashSet |
collections.Counter, defaultdict |
HashMap dengan entry API: *map.entry(k).or_insert(0) += 1 |
HashMap |
collections.deque |
VecDeque<T> |
VecDeque dan BinaryHeap |
heapq (yang terkecil duluan) |
BinaryHeap<T> (yang terbesar duluan; bungkus dengan Reverse agar yang terkecil duluan) |
VecDeque dan BinaryHeap |
namedtuple |
struct |
Struct |
bytes, bytearray |
&[u8], Vec<u8> |
Slice |
Slicing: xs[1:3] |
Slice: &xs[1..3] (tidak ada indeks negatif; pakai xs.len() - 1) |
Slice |
Tooling: dari pip ke cargo
Section titled “Tooling: dari pip ke cargo”Di bagian inilah banyak developer Python bisa sedikit bernapas lega. Rust punya satu tool resmi, Cargo, yang mengerjakan tugas pip, venv, setuptools, pytest, dan lainnya. Tidak ada perdebatan “tool packaging mana yang sebaiknya kita pakai?”.
| Python | Rust | Catatan |
|---|---|---|
Interpreter python |
Compiler rustc, biasanya dipanggil lewat cargo |
Kamu compile dulu, lalu menjalankan binary native. Lihat Mulai dengan Rust |
| pyenv | rustup (dan file rust-toolchain.toml untuk mengunci versi) |
Lihat Tools dan setup Rust |
| pip, poetry, uv | cargo |
Mem-build, menjalankan, mengetes, menambah package, mem-publish. Lihat Cargo dan crate |
pip install requests |
cargo add reqwest |
Menambahkan crate ke Cargo.toml, hanya untuk proyek ini |
pyproject.toml / requirements.txt |
Cargo.toml |
|
poetry.lock / uv.lock |
Cargo.lock |
Dibuat otomatis untukmu |
| venv / virtualenv | Tidak perlu | Setiap proyek punya dependency sendiri dan di-build ke folder target/ miliknya sendiri |
| PyPI | crates.io | Dokumentasi API setiap crate yang dipublikasikan ada di docs.rs |
python main.py |
cargo run (cargo run --release untuk kecepatan penuh) |
Build debug jauh lebih lambat saat dijalankan; ukur kecepatan dalam mode release |
pipx install ruff |
cargo install ripgrep |
Menginstal tool command-line |
| PyInstaller | cargo build --release |
Hasilnya satu binary native; pengguna tidak perlu menginstal Rust |
| black | cargo fmt |
Satu gaya standar untuk semua orang |
| ruff, pylint, flake8 | cargo clippy |
Ratusan lint yang membantu, resmi |
| mypy, pyright | Compiler-nya sendiri | Pengecekan tipe tidak opsional dan bukan langkah terpisah |
| pytest, unittest | #[test] bawaan dan cargo test (cargo nextest adalah runner populer yang lebih cepat) |
Lihat Testing |
Fixture pytest dan parametrize |
rstest |
|
| hypothesis | proptest |
Property-based testing. Lihat Pengujian lanjutan |
unittest.mock |
mockall, atau fake buatan sendiri yang mengimplementasikan trait-mu |
|
| timeit, pytest-benchmark | criterion |
Lihat Performa |
| cProfile, py-spy | cargo flamegraph, perf, atau profiler native apa saja |
|
| Docstring + Sphinx | Doc comment /// dalam Markdown + cargo doc |
Contoh kode di dokumentasi dijalankan sebagai test. Lihat Dokumentasi |
| REPL Python, Jupyter | Rust Playground; evcxr_repl dan evcxr_jupyter |
Rust tidak punya REPL bawaan. Proyek kecil dengan cargo run adalah cara yang biasa untuk bereksperimen |
| VS Code + Pylance, PyCharm | VS Code + rust-analyzer, atau JetBrains RustRover | rust-analyzer memberi completion, go-to-definition, dan error langsung di baris kode |
Ini contoh Cargo.toml kecil, supaya kamu bisa membandingkannya dengan pyproject.toml:
[package]name = "weather-cli"version = "0.1.0"edition = "2021"
[dependencies]reqwest = { version = "0.13", features = ["blocking", "json"] }serde = { version = "1", features = ["derive"] }clap = { version = "4", features = ["derive"] }Library dan framework: “Kalau begitu, pakai apa sebagai gantinya…?”
Section titled “Library dan framework: “Kalau begitu, pakai apa sebagai gantinya…?””“Aku pakai requests, FastAPI, dan pandas. Versi Rust-nya apa?” Berikut padanan yang paling dekat. Ekosistem Python jauh lebih tua dan lebih besar, jadi di beberapa bidang (terutama data science) pilihan di Rust lebih sedikit dan lebih muda.
Web dan networking
Section titled “Web dan networking”| Python | Rust | Catatan |
|---|---|---|
| Flask, FastAPI | axum (kita membuatnya di Proyek: Web API dengan Axum), actix-web | Handler-nya berupa async fn biasa; tipe request dan response berasal dari struct-mu, agak mirip FastAPI dengan pydantic |
| Django | Tidak ada padanan yang lengkap. Loco (loco-rs) adalah framework yang serba ada; kebanyakan tim menggabungkan axum + ORM + template engine |
Tidak ada situs admin bawaan |
| requests, httpx | reqwest (async, dengan client blocking opsional), ureq (sederhana dan blocking) |
Lihat Jaringan di Rust |
| aiohttp | reqwest + tokio |
|
| Jinja2 | minijinja (dari pembuat Jinja sendiri), tera, askama (template dicek saat compile) |
|
| BeautifulSoup | scraper |
Data dan database
Section titled “Data dan database”| Python | Rust | Catatan |
|---|---|---|
| ORM SQLAlchemy | Diesel (query dicek tipenya saat compile), SeaORM (async, gaya entity) | Database di Rust menunjukkan keduanya dengan kode yang benar-benar jalan |
| psycopg, SQL mentah | sqlx: SQL biasa, dicek terhadap database sungguhan saat compile | Async, sangat populer, bukan ORM |
| sqlite3 | rusqlite |
|
| redis-py | redis |
|
| Celery | apalis untuk antrean job; tokio::spawn untuk background task di dalam proses |
Data science dan machine learning
Section titled “Data science dan machine learning”| Python | Rust | Catatan |
|---|---|---|
| pandas | Polars | Ditulis dengan Rust; API Python-nya, yang mungkin sudah kamu kenal, dibangun di atas library Rust yang sama |
| NumPy | ndarray |
Array N-dimensi dengan slicing dan broadcasting; fitur tambahannya jauh lebih sedikit daripada NumPy |
| scikit-learn | linfa, smartcore |
Algoritmanya lebih sedikit dan komunitasnya jauh lebih kecil |
| PyTorch | candle (crate candle-core), burn |
Bagus untuk menjalankan model; sebagian besar training masih dilakukan di Python |
| matplotlib | plotters |
|
Modul csv |
Crate csv + serde |
Komponen sehari-hari
Section titled “Komponen sehari-hari”| Python | Rust | Catatan |
|---|---|---|
json |
serde + serde_json |
Standarnya; juga bisa TOML, YAML, dan lainnya. Lihat JSON dengan Serde |
| pydantic | serde untuk parsing + validator atau garde untuk aturan validasi |
Struct-mu adalah skemanya |
| argparse, click, typer | clap |
Definisikan argumenmu sebagai struct. Lihat Proyek: CLI dengan clap |
logging |
tracing, atau log + env_logger |
Lihat Logging |
re |
regex |
Tidak ada look-around atau backreference, tapi dijamin berjalan dalam waktu linear; fancy-regex punya keduanya. Lihat Regular expression |
datetime |
chrono atau jiff |
Lihat Tanggal dan waktu |
random |
rand |
Lihat Angka acak |
asyncio |
tokio |
Lihat Async/Await |
multiprocessing.Pool.map |
rayon: ganti .iter() dengan .par_iter() |
|
itertools |
Sebagian besar sudah ada di Iterator; crate itertools menambahkan sisanya |
|
os, pathlib, subprocess |
std::env, std::path, std::fs, std::process |
Lihat Membaca dan menulis file, Environment dan proses |
Berdampingan: class menjadi struct + impl
Section titled “Berdampingan: class menjadi struct + impl”Ini sebuah class Python kecil:
class Account: def __init__(self, owner, balance=0): self.owner = owner self.balance = balance
def deposit(self, amount): self.balance += amount
def __str__(self): return f"{self.owner} has {self.balance}"
account = Account("Ada", 100)account.deposit(50)print(account)Dan hal yang sama di Rust:
use std::fmt;
struct Account { owner: String, balance: i64,}
impl Account { // `new` hanya kebiasaan, bukan keyword. Tidak ada argumen default, // jadi pemanggil selalu memberikan kedua nilainya. fn new(owner: &str, balance: i64) -> Self { Account { owner: owner.to_string(), balance } }
// `&mut self`: method ini mengubah rekeningnya. fn deposit(&mut self, amount: i64) { self.balance += amount; }}
// Inilah __str__ versi Rust.impl fmt::Display for Account { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{} punya {}", self.owner, self.balance) }}
fn main() { let mut account = Account::new("Ada", 100); account.deposit(50); println!("{account}");}Output:
Ada punya 150Ada beberapa hal yang berbeda. Field-nya dideklarasikan di awal beserta tipenya, jadi kamu tidak bisa menambahkan atribut baru belakangan seperti account.nickname = "A" di Python. Data (struct) dan perilaku (impl) tinggal di dua blok yang terpisah. Setiap method menyatakan apakah ia hanya membaca (&self) atau mengubah (&mut self) nilainya, dan karena deposit mengubah rekening, variabelnya harus dideklarasikan dengan let mut. Rust tidak punya argumen default; kalau kamu ingin rekening yang dimulai dari nol, tambahkan fungsi kedua, misalnya fn empty(owner: &str) -> Self.
Berdampingan: duck typing menjadi trait
Section titled “Berdampingan: duck typing menjadi trait”Python tidak peduli sebuah objek berasal dari class apa, yang penting ia punya method yang tepat:
class Duck: def speak(self): return "Quack"
class Robot: def speak(self): return "Beep boop"
def introduce(thing): print(f"It says: {thing.speak()}")
for thing in [Duck(), Robot()]: introduce(thing)
introduce(42) # AttributeError, tapi baru saat baris ini dijalankanDi Rust, “punya method speak” ditulis sebagai sebuah trait, dan setiap tipe ikut serta lewat blok impl:
trait Speak { fn speak(&self) -> String;}
struct Duck;struct Robot;
impl Speak for Duck { fn speak(&self) -> String { "Quack".to_string() }}
impl Speak for Robot { fn speak(&self) -> String { "Beep boop".to_string() }}
// Menerima tipe apa pun yang mengimplementasikan Speak.fn introduce(thing: &dyn Speak) { println!("Katanya: {}", thing.speak());}
fn main() { let things: Vec<Box<dyn Speak>> = vec![Box::new(Duck), Box::new(Robot)]; for thing in &things { introduce(thing.as_ref()); }
// introduce(&42); // error compile: `{integer}` tidak mengimplementasikan Speak}Output:
Katanya: QuackKatanya: Beep boopJiwanya masih duck typing, tapi dicek oleh compiler: introduce(&42) sudah menjadi error sebelum program dijalankan, bukan AttributeError di production. Kalau kamu pernah memakai typing.Protocol, trait akan terasa wajar. List berisi tipe campuran butuh Box<dyn Speak>, karena Rust harus tahu cara memanggil method pada setiap item (lihat Trait object). Dan kamu bisa mengimplementasikan trait buatanmu untuk tipe yang bukan buatanmu, seperti String atau i32.
Berdampingan: None menjadi Option
Section titled “Berdampingan: None menjadi Option”Di Python, nilai apa pun diam-diam bisa berisi None, dan kamu baru tahu saat memakainya:
def find_email(user_id): return emails.get(user_id) # mengembalikan None kalau tidak ada
print(find_email(1).upper())print(find_email(3).upper()) # AttributeError: 'NoneType' object has no attribute 'upper'Di Rust, nilai yang mungkin tidak ada bertipe Option<T>. Kamu tidak bisa memakainya sebagai nilai biasa sebelum menangani kasus None:
fn find_email(user_id: u32) -> Option<String> { match user_id { _ => None, }}
fn main() { for id in [1, 3] { match find_email(id) { Some(email) => println!("{id}: {}", email.to_uppercase()), None => println!("{id}: email tidak terdaftar"), } }
// Seperti `find_email(3) or "unknown"` di Python. let email = find_email(3).unwrap_or("tidak diketahui".to_string()); println!("{email}");
// Ubah nilai di dalamnya, kalau ada. let upper = find_email(2).map(|e| e.to_uppercase()); println!("{upper:?}");}Output:
3: email tidak terdaftartidak diketahuiSome("[email protected]")Compiler Rust tidak akan membiarkanmu memanggil .to_uppercase() pada Option<String>, jadi error “'NoneType' object has no attribute” memang tidak mungkin terjadi. String di Rust selalu berisi string sungguhan. Pelajari method-method pembantunya di halaman Option, dan bentuk singkat if let Some(email) = ... di If Let / While Let.
Berdampingan: exception menjadi Result dan ?
Section titled “Berdampingan: exception menjadi Result dan ?”Di Python, int() melempar exception, dan kamu menangkapnya di suatu tempat di atas call stack:
def parse_port(text): port = int(text) # bisa melempar ValueError if not 1 <= port <= 65535: raise ValueError(f"port {port} is out of range") return port
for text in ["8080", "http", "70000"]: try: print(f"port {parse_port(text)}") except ValueError as e: print(f"error: {e}")Di Rust, fungsi yang bisa gagal menyatakannya di tipe kembaliannya. Operator ? berarti “kalau ini gagal, kembalikan error-nya ke pemanggilku sekarang juga”. Fungsinya sama seperti membiarkan exception naik ke atas, tapi kamu bisa melihatnya di kode:
fn parse_port(text: &str) -> Result<u32, String> { let port: u32 = text .parse() .map_err(|e| format!("{text:?} bukan angka ({e})"))?;
if !(1..=65535).contains(&port) { return Err(format!("port {port} di luar rentang")); } Ok(port)}
fn main() { for text in ["8080", "http", "70000"] { match parse_port(text) { Ok(port) => println!("port {port}"), Err(e) => println!("error: {e}"), } }}Output:
port 8080error: "http" bukan angka (invalid digit found in string)error: port 70000 di luar rentangPesan invalid digit found in string berasal dari standard library Rust, jadi tetap dalam bahasa Inggris. Artinya: ada karakter yang bukan angka di dalam string.
Perbedaan besarnya: di Python, fungsi apa pun bisa melempar exception, dan signature-nya tidak memberitahumu. Di Rust, Result adalah bagian dari signature, jadi pemanggil tidak bisa melupakannya. String sudah cukup sebagai tipe error selama kamu belajar; proyek sungguhan memakai enum error sendiri (Error kustom) atau crate anyhow, yang terasa mirip “lempar exception apa saja” (anyhow dan thiserror).
Berdampingan: comprehension dan dict menjadi iterator dan HashMap
Section titled “Berdampingan: comprehension dan dict menjadi iterator dan HashMap”List comprehension dan menghitung dengan dict adalah Python sehari-hari:
text = "the cat sat on the mat the end"
loud = [w.upper() for w in text.split() if len(w) == 3]print(loud)
counts = {}for word in text.split(): counts[word] = counts.get(word, 0) + 1
top = sorted(counts.items(), key=lambda kv: (-kv[1], kv[0]))[:3]for word, n in top: print(f"{word}: {n}")Di Rust, comprehension menjadi rantai iterator yang diakhiri dengan collect(), dan dict menjadi HashMap:
use std::collections::HashMap;
fn main() { let text = "the cat sat on the mat the end";
// [w.upper() for w in text.split() if len(w) == 3] let loud: Vec<String> = text .split_whitespace() .filter(|w| w.len() == 3) .map(|w| w.to_uppercase()) .collect(); println!("{loud:?}");
// counts[word] = counts.get(word, 0) + 1 let mut counts: HashMap<&str, u32> = HashMap::new(); for word in text.split_whitespace() { *counts.entry(word).or_insert(0) += 1; }
// Yang paling sering duluan; kalau seri, urut abjad. let mut top: Vec<(&str, u32)> = counts.into_iter().collect(); top.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(b.0))); for (word, n) in top.iter().take(3) { println!("{word}: {n}"); }}Output:
["THE", "CAT", "SAT", "THE", "MAT", "THE", "END"]the: 3cat: 1end: 1Kalimat contohnya sengaja dibiarkan dalam bahasa Inggris: kode ini menyaring kata berdasarkan panjangnya dan mengurutkannya menurut abjad, jadi kata-kata lain akan memberi output yang berbeda.
Rantainya dibaca dari kiri ke kanan, bukan dari dalam ke luar, tapi idenya sama. Seperti generator expression, rantai ini lazy: tidak ada yang terjadi sampai collect(), sum(), count(), atau perulangan for menarik nilai-nilainya. collect() perlu tahu apa yang harus dibuat, karena itulah loud bertipe Vec<String>. Perhatikan juga langkah pengurutannya: dict di Python mengingat urutan penyisipan, tapi HashMap di Rust tidak, jadi kita selalu mengurutkan dulu sebelum mencetak. Pengurutan dibahas di halaman Mengurutkan dan mencari.
Berdampingan: GIL vs thread sungguhan
Section titled “Berdampingan: GIL vs thread sungguhan”Di CPython standar, Global Interpreter Lock (GIL) hanya mengizinkan satu thread menjalankan bytecode Python pada satu waktu. Thread membantu saat kamu menunggu jaringan, tapi pekerjaan berat di CPU seperti ini tidak menjadi lebih cepat dengan menambah thread, jadi developer Python memilih multiprocessing:
from concurrent.futures import ThreadPoolExecutor
def is_prime(n): if n < 2: return False d = 2 while d * d <= n: if n % d == 0: return False d += 1 return True
def count_primes(lo, hi): return sum(1 for n in range(lo, hi) if is_prime(n))
ranges = [(0, 25_000), (25_000, 50_000), (50_000, 75_000), (75_000, 100_000)]with ThreadPoolExecutor(4) as pool: counts = list(pool.map(lambda r: count_primes(*r), ranges))print("total:", sum(counts))Rust tidak punya GIL. Setiap thread::spawn adalah thread sistem operasi sungguhan, dan semuanya berjalan bersamaan di core CPU yang berbeda:
use std::thread;
fn is_prime(n: u32) -> bool { if n < 2 { return false; } let mut d = 2; while d * d <= n { if n % d == 0 { return false; } d += 1; } true}
fn count_primes(lo: u32, hi: u32) -> usize { (lo..hi).filter(|&n| is_prime(n)).count()}
fn main() { let ranges = [(0, 25_000), (25_000, 50_000), (50_000, 75_000), (75_000, 100_000)];
// Setiap rentang mendapat thread OS sendiri. Semuanya benar-benar berjalan bersamaan. let counts: Vec<usize> = thread::scope(|s| { let handles: Vec<_> = ranges .iter() .map(|&(lo, hi)| s.spawn(move || count_primes(lo, hi))) .collect(); handles.into_iter().map(|h| h.join().unwrap()).collect() });
for ((lo, hi), count) in ranges.iter().zip(&counts) { println!("{lo}..{hi}: {count} bilangan prima"); } println!("total: {}", counts.iter().sum::<usize>());}Output:
0..25000: 2762 bilangan prima25000..50000: 2371 bilangan prima50000..75000: 2260 bilangan prima75000..100000: 2199 bilangan primatotal: 9592thread::scope menunggu semua thread selesai sebelum ia kembali, jadi thread-thread itu aman meminjam data lokal. Tanpa GIL, mungkin kamu khawatir dua thread mengubah nilai yang sama secara bersamaan. Rust menangani itu saat compile: data bersama yang bisa diubah harus dibungkus dengan sesuatu seperti Arc<Mutex<T>>, kalau tidak programnya tidak mau di-compile (lihat Shared state). Di proyek sungguhan, crate rayon membuatnya lebih pendek lagi, agak mirip multiprocessing.Pool.map tapi dengan thread:
use rayon::prelude::*;
let total = (0..100_000u32).into_par_iter().filter(|&n| is_prime(n)).count();Python juga sedang berubah di sini: Python 3.13 menambahkan build “free-threaded” opsional tanpa GIL. Tapi CPython standar yang diinstal dan di-deploy kebanyakan orang masih punya GIL, dan banyak extension C belum siap untuk free threading.
Menulis extension Python dengan Rust (PyO3 + maturin)
Section titled “Menulis extension Python dengan Rust (PyO3 + maturin)”Kamu tidak perlu meninggalkan Python untuk memakai Rust. PyO3 memungkinkanmu menulis modul Python dengan Rust, dan maturin mem-build-nya menjadi wheel biasa yang bisa diinstal dengan pip. Beginilah cara Polars, pydantic-core, dan banyak library Python cepat lainnya dibuat.
Siapkan proyeknya (di dalam virtual environment yang sudah aktif):
pip install maturinmaturin new --bindings pyo3 fastmathcd fastmathmaturin menuliskan Cargo.toml dan pyproject.toml untukmu. Lalu taruh fungsi-fungsi Rust-mu di src/lib.rs:
use pyo3::prelude::*;
/// Modul Python yang ditulis dengan Rust.#[pymodule]mod fastmath { use pyo3::prelude::*;
/// Menjumlahkan kuadrat dari sebuah list angka. #[pyfunction] fn sum_of_squares(numbers: Vec<i64>) -> i64 { numbers.iter().map(|n| n * n).sum() }
/// Menghitung jumlah kata dalam sebuah teks. #[pyfunction] fn word_count(text: &str) -> usize { text.split_whitespace().count() }}Build ke dalam virtual environment-mu dengan maturin develop (tambahkan --release kalau kamu peduli soal kecepatan), lalu pakai dari Python seperti modul lainnya:
import fastmath
print(fastmath.sum_of_squares([1, 2, 3, 4])) # 30print(fastmath.word_count("the quick brown fox")) # 4fastmath.sum_of_squares(["a"]) # TypeError: 'str' object cannot be interpreted as an integerPyO3 mengonversi tipe untukmu: list Python berisi int menjadi Vec<i64>, str menjadi &str, dan doc comment menjadi docstring Python. Tipe yang salah berubah menjadi TypeError Python biasa. Saat kamu siap membagikannya, maturin build --release membuat wheel, dan maturin publish mengunggahnya ke PyPI. Aturan yang bagus: ukur dulu, lalu pindahkan hanya perulangan yang paling sibuk ke Rust. Berpindah antara Python dan Rust punya sedikit biaya, jadi satu pemanggilan yang mengerjakan banyak hal lebih baik daripada sejuta pemanggilan kecil.
Hal-hal yang akan membuatmu tersandung sebagai developer Python
Section titled “Hal-hal yang akan membuatmu tersandung sebagai developer Python”Semua orang yang datang dari Python menabrak polisi tidur yang sama. Mengetahuinya lebih awal akan membantu.
Assignment memindahkan nilai
Section titled “Assignment memindahkan nilai”Di Python, b = a memberimu dua nama untuk list yang sama. Di Rust, let b = a; memindahkan (move) ownership sebuah Vec atau String ke b, dan a tidak bisa dipakai lagi:
fn main() { let a = vec![1, 2, 3]; let mut b = a; b.push(4); println!("{a:?}");}error[E0382]: borrow of moved value: `a` --> src/main.rs:5:16 |2 | let a = vec![1, 2, 3]; | - move occurs because `a` has type `Vec<i32>`, which does not implement the `Copy` trait3 | let mut b = a; | - value moved here4 | b.push(4);5 | println!("{a:?}"); | ^ value borrowed here after move |help: consider cloning the value if the performance cost is acceptable |3 | let mut b = a.clone(); | ++++++++Compiler memberitahumu bahwa a sudah dipindahkan di baris 3 (“value moved here”), lalu kamu mencoba memakainya lagi di baris 5. Solusinya adalah meminjam (let b = &a;, kalau kamu hanya perlu membaca) atau sengaja membuat salinan sungguhan (a.clone()). Satu aturan ini, ownership, adalah ide baru yang paling penting untukmu. Baca halaman itu dan Borrowing pelan-pelan.
Daftar lainnya
Section titled “Daftar lainnya”- Integer punya ukuran.
intdi Python tidak pernah overflow.i32di Rust berhenti di 2.147.483.647; melewatinya akan panic di build debug. Pilihi64atauu64untuk angka besar, dan lihat Integer overflow. - Pembagian dan sisa bagi bekerja secara berbeda.
7 / 2hasilnya3di Rust, karena kedua sisinya integer (seperti//di Python). Tulis7.0 / 2.0untuk mendapatkan3.5. Selain itu,-7 % 3hasilnya-1di Rust tapi2di Python; pakai(-7_i32).rem_euclid(3)untuk mendapatkan jawaban versi Python. - Tipe angka tidak boleh dicampur.
let x: f64 = 2.5 * 3;tidak bisa di-compile, karena3adalah integer. Tulis3.0, atau konversi denganas f64(lihat Konversi tipe). - String tidak bisa diakses dengan indeks.
word[0]adalah error compile, karena string Rust berformat UTF-8 dan satu “karakter” bisa terdiri dari beberapa byte. Pakaiword.chars().next()atauword.chars().nth(i). Lihat String. Stringvs&str. Python punya satu tipestr. Rust punya dua:Stringmemiliki teksnya,&strmeminjamnya. Fungsi biasanya menerima&str, dan struct biasanya menyimpanString.- Variabel immutable secara default. Tulis
let mutkalau sebuah nilai perlu berubah. Compiler akan memberitahumu kalau kamu lupa. - Kurung kurawal dan titik koma. Indentasi tidak berarti apa-apa bagi Rust;
{ }-lah yang penting. Dan baris terakhir sebuah fungsi tanpa titik koma adalah nilai kembaliannya:fn double(x: i32) -> i32 { x * 2 }. - Setiap
ifdanmatchharus lengkap dan tipenya benar. Tidak ada truthiness:if counttidak bisa di-compile, kamu harus menulisif count > 0. Sebuahmatchharus mencakup semua kasus. - HashMap tidak punya urutan.
dictdi Python mengingat urutan penyisipan;HashMapdi Rust tidak. Urutkan dulu sebelum mencetak, pakaiBTreeMap, atau tambahkan crateindexmap. - Ada tahap compile. Tidak ada REPL, dan clean build sebuah proyek dengan banyak crate butuh waktu. Pakai
cargo checkselagi kamu mengedit (ia melewati tahap pembuatan kode dan jauh lebih cepat), dancargo run --releasesaat kamu mengukur kecepatan. - Borrow checker akan melawan. Pola yang biasa di Python, seperti banyak objek yang sama-sama memegang dan mengubah satu objek bersama, atau objek yang menunjuk balik ke parent-nya, butuh smart pointer atau desain yang berbeda. Bersiaplah untuk beberapa minggu yang bikin frustrasi. Setelahnya jauh lebih mudah.
Jalur belajar untuk developer Python
Section titled “Jalur belajar untuk developer Python”Kamu tidak perlu membaca situs ini berurutan. Sebagai developer Python, rute ini memberi waktu lebih untuk bagian Rust yang paling berbeda dari yang sudah kamu tahu:
- Mulai dengan Rust dan Tools dan setup Rust: instal Rust dan jalankan
cargo new. - Variabel, Tipe data, dan Integer overflow: tipe adalah kebiasaan baru yang pertama.
- Fungsi, If…Else, dan Loop: cepat dibaca; perhatikan ekspresi dan titik koma yang tidak ditulis.
- Ownership, lalu Borrowing, lalu Slice: halaman paling penting untukmu. Jangan terburu-buru.
- String:
Stringvs&str, dan kenapa string tidak bisa diakses dengan indeks. - Struct dan Enum, lalu Match dan Pattern matching.
- Option dan Result, lalu Error kustom.
- Vektor, HashMap, dan HashSet.
- Closure dan Iterator: comprehension dan generator versimu.
- Trait, Generik, Trait yang sering dipakai, dan Trait object.
- Modul, Cargo dan crate, dan Testing.
- Lifetime dan Smart pointer.
- Konkurensi, Shared state, dan Async/Await.
- JSON dengan Serde, lalu bangun CLI dengan clap atau Web API.
Latihan
Section titled “Latihan”Kedua latihan memberimu potongan kode Python. Porting kode itu ke Rust.
1. Porting comprehension dan dict
Section titled “1. Porting comprehension dan dict”Porting kode Python ini. Petunjuk: untuk mencetak nama secara berurutan, kumpulkan ke dalam Vec<&str>, panggil .sort(), lalu .join(", "). Untuk rata-ratanya, konversi dengan as f64 sebelum membagi.
scores = {"Ada": 92, "Bob": 47, "Cy": 75, "Dee": 38}
passed = sorted(name for name, score in scores.items() if score >= 50)print("passed:", ", ".join(passed))
odd_squares = [n * n for n in range(1, 10) if n % 2 == 1]print(odd_squares)
average = sum(scores.values()) / len(scores)print(f"average: {average:.1f}")Lihat solusi
use std::collections::HashMap;
fn main() { let scores: HashMap<&str, u32> = HashMap::from([("Ada", 92), ("Bob", 47), ("Cy", 75), ("Dee", 38)]);
let mut passed: Vec<&str> = scores .iter() .filter(|(_, score)| **score >= 50) .map(|(name, _)| *name) .collect(); passed.sort(); println!("lulus: {}", passed.join(", "));
let odd_squares: Vec<u32> = (1..10).filter(|n| n % 2 == 1).map(|n| n * n).collect(); println!("{odd_squares:?}");
let total: u32 = scores.values().sum(); let average = total as f64 / scores.len() as f64; println!("rata-rata: {average:.1}");}Output:
lulus: Ada, Cy[1, 9, 25, 49, 81]rata-rata: 63.0Melakukan iterasi atas HashMap memberimu pasangan referensi, karena itulah kamu melihat **score dan *name: keduanya mengikuti referensi kembali ke nilainya. sort() di sini tidak opsional: tanpanya, nama-nama itu bisa keluar dalam urutan apa saja. Dan total as f64 diperlukan karena Rust tidak pernah diam-diam mengubah integer menjadi float.
2. Porting try/except ke Result
Section titled “2. Porting try/except ke Result”Porting kode Python ini. Setiap baris seharusnya berformat nama: umur. Petunjuk: line.split_once(':') mengembalikan Option<(&str, &str)>; ubah menjadi Result dengan .ok_or(...), lalu pakai ?. Buat fungsimu mengembalikan Result<(String, u32), String>.
def parse_line(line): if ":" not in line: raise ValueError("missing ':'") name, age = line.split(":", 1) try: return name.strip(), int(age.strip()) except ValueError as e: raise ValueError(f"bad age: {e}")
ages = {}for line in ["Ada: 36", "Grace 45", "Linus: forty"]: try: name, age = parse_line(line) ages[name] = age print(f"ok: {name} is {age}") except ValueError as e: print(f"skipped {line!r}: {e}")print(len(ages), "saved")Lihat solusi
use std::collections::HashMap;
fn parse_line(line: &str) -> Result<(String, u32), String> { let (name, age) = line.split_once(':').ok_or("tidak ada ':'")?; let age: u32 = age.trim().parse().map_err(|e| format!("umur tidak valid: {e}"))?; Ok((name.trim().to_string(), age))}
fn main() { let mut ages = HashMap::new();
for line in ["Ada: 36", "Grace 45", "Linus: empat puluh"] { match parse_line(line) { Ok((name, age)) => { println!("ok: {name} berumur {age}"); ages.insert(name, age); } Err(e) => println!("dilewati {line:?}: {e}"), } }
println!("{} tersimpan", ages.len());}Output:
ok: Ada berumur 36dilewati "Grace 45": tidak ada ':'dilewati "Linus: empat puluh": umur tidak valid: invalid digit found in string1 tersimpanKedua raise menjadi nilai Err, dan try/except menjadi match. Tanda ? setelah ok_or("tidak ada ':'") tetap berfungsi meskipun error-nya bertipe &str sementara fungsinya mengembalikan error String, karena ? mengonversi error-nya dengan From. Perhatikan urutan di dalam Ok: kita mencetak dulu, baru insert memindahkan name ke dalam map. Tukar kedua baris itu, dan compiler akan memberitahumu bahwa name sudah dipindahkan.
Ringkasan
Section titled “Ringkasan”- Banyak hal dari Python tetap terpakai: perulangan atas iterator, tuple, closure,
match, dan pengolahan data ala comprehension (dalam bentuk rantai iterator). - Perubahan besarnya: tipe statis yang dicek sebelum program berjalan, ownership menggantikan garbage collector,
OptionmenggantikanNone, danResult+?menggantikan exception. - Class menjadi
struct+impl, duck typing menjadi trait, dan blokwithmenjadiDropyang otomatis.HashMaptidak punya urutan, dan integer punya ukuran tetap. - Rust tidak punya GIL: thread berjalan paralel, dan compiler mencegah data race.
- Cargo menggantikan pip, venv, dan kawan-kawannya;
cargo fmt,cargo clippy, dancargo testmenggantikan black, ruff, dan pytest. - Kamu bisa memakai kedua bahasa sekaligus: PyO3 dan maturin mengubah kode Rust menjadi modul Python.
Kuis singkat
Section titled “Kuis singkat”Kuis singkat
1. Apa yang dipakai Rust sebagai pengganti None di Python?
2. Di Python, b = a memberi dua nama untuk list yang sama. Apa yang dilakukan let b = a; pada sebuah Vec di Rust?
3. Berapa hasil 7 / 2 di Rust, kalau kedua angkanya integer?
4. Kamu ingin mempercepat satu fungsi lambat di proyek Python dengan Rust. Tool apa yang kamu pakai?
Skor: 0 / 4
Bacaan lebih lanjut
Section titled “Bacaan lebih lanjut”- The Rust Programming Language (bahasa Inggris): buku resmi Rust. Bab 4 (ownership), 6 (enum dan
Option), 9 (error), dan 13 (closure dan iterator) yang paling penting untuk developer Python. - PyO3 user guide (bahasa Inggris): semua tentang memanggil Rust dari Python (dan Python dari Rust).
- Maturin user guide (bahasa Inggris): mem-build, mengetes, dan mem-publish package Python yang ditenagai Rust.
- Rust by Example (bahasa Inggris): contoh-contoh pendek yang bisa dijalankan, cocok kalau kamu paling cepat belajar dengan membaca kode.
- Are We Learning Yet? (bahasa Inggris): gambaran umum crate machine learning dan data di Rust.