Pemrograman · Bahasa pemrograman
Tutorial Rust untuk pemula
Jawaban singkat
Rust adalah bahasa sistem terkompilasi dengan tipe statis yang secepat C dan C++ tetapi mencegah kesalahan memori saat kompilasi, tanpa garbage collector. Instal dengan rustup, jalankan cargo new hello dan cargo run. Konsep intinya: ownership dan borrowing (diperiksa oleh borrow checker), struct dan enum dengan match, collection seperti Vec dan HashMap, serta penanganan error dengan Result dan operator ?.
Apa itu Rust dan untuk apa dipakai
Rust bermula sebagai proyek pribadi Graydon Hoare, disponsori Mozilla, dan mencapai versi 1.0 pada 2015. Kini Rust diurus oleh Rust Foundation. Rust dipakai di tempat yang menuntut kecepatan sekaligus keandalan: tool command line (ripgrep), layanan web, komponen browser di Firefox, bagian dari kernel Linux dan Windows, perangkat embedded, dan WebAssembly. Compiler-nya ketat, tetapi sebagai gantinya satu kelas bug penuh, seperti use-after-free dan data race, tidak bisa terjadi pada kode Rust yang aman.
Instal Rust dan jalankan Hello, world
Instal toolchain dengan rustup dari rustup.rs. Ia menyediakan rustc (compiler) dan Cargo (build tool sekaligus package manager). Buat proyek:
cargo new hello
cd hello
cargo run
# Hello, world!Cargo membuat src/main.rs:
fn main() {
println!("Hello, world!");
}println! diakhiri ! karena ia adalah macro, bukan fungsi. Macro menghasilkan kode saat kompilasi; di sini, format string diperiksa ketika Anda mengompilasi.
Variabel dan tipe data dasar
Variabel bersifat immutable secara default; tambahkan mut untuk mengizinkan perubahan. Tipe biasanya disimpulkan, tetapi setiap nilai memiliki tepat satu tipe statis, dan konversi numerik dilakukan eksplisit dengan as.
| Tipe | Contoh | Catatan |
|---|---|---|
i32, i64, u8, usize | let n: i32 = 42; | Bertanda (i) dan tak bertanda (u); usize untuk indeks |
f64 | let x = 3.14; | Tipe float default |
bool | let ok = true; | true atau false |
char | let c = 'é'; | Sebuah Unicode scalar value, 4 byte |
&str | let s = "hi"; | String slice pinjaman (borrowed) |
String | String::from("hi") | Teks UTF-8 milik sendiri (owned) yang dapat bertambah |
| Tuple, array | (1, "a"), [0; 3] | Ukuran tetap |
fn main() {
let name = "Ada";
let mut count = 7;
count += 1;
let avg = count as f64 / 2.0;
println!("{name} has {count} items, avg {avg}"); // Ada has 8 items, avg 4
let parsed: i32 = "12".parse().unwrap();
println!("{}", parsed + 1); // 13
}Alur kontrol dan fungsi
if, match, dan blok adalah ekspresi: mereka menghasilkan nilai. Ekspresi terakhir sebuah fungsi, tanpa titik koma, adalah nilai kembaliannya.
fn square(x: i32) -> i32 {
x * x
}
fn main() {
let score = 72;
let grade = if score >= 90 { "A" } else if score >= 70 { "B" } else { "C" };
println!("{grade}"); // B
for i in 0..3 {
print!("{} ", square(i)); // 0 1 4
}
println!();
let mut n = 10;
while n > 1 {
n /= 2;
}
let label = loop {
break "done"; // loop can return a value with break
};
println!("{n} {label}"); // 1 done
}Fitur khas: ownership dan borrowing
Setiap nilai punya tepat satu owner. Ketika owner keluar dari scope, nilai itu di-drop (dibebaskan). Menugaskan nilai heap seperti String ke variabel lain akan memindahkannya (move), dan nama lama tidak bisa dipakai lagi. Untuk memakai nilai tanpa mengambil kepemilikan, pinjam dengan reference: sejumlah reference bersama &T, atau tepat satu reference mutable &mut T, pada satu waktu.
fn length(s: &str) -> usize {
s.len() // borrows, does not take ownership
}
fn add_exclamation(s: &mut String) {
s.push('!');
}
fn main() {
let a = String::from("hello");
let b = a; // ownership moves to b
// println!("{a}"); // error: borrow of moved value: `a`
let mut c = b.clone(); // explicit deep copy
add_exclamation(&mut c);
println!("{} {} {}", b, c, length(&c)); // hello hello! 6
}Aturan ini ditegakkan oleh borrow checker saat kompilasi, dan itulah cara Rust mencapai keamanan memori tanpa garbage collector. Tipe sederhana seperti integer mengimplementasikan Copy, sehingga disalin, bukan dipindahkan.
Struct, enum, dan pattern matching
Struct mengelompokkan field, dan blok impl menambahkan method. Enum dapat membawa data di setiap variant, dan match harus menangani setiap variant, yang diperiksa oleh compiler. Enum standar Option<T> (Some(value) atau None) menggantikan null.
#[derive(Debug)]
struct Point {
x: f64,
y: f64,
}
impl Point {
fn distance(&self) -> f64 {
(self.x * self.x + self.y * self.y).sqrt()
}
}
enum Shape {
Circle(f64),
Rect { w: f64, h: f64 },
}
fn area(s: &Shape) -> f64 {
match s {
Shape::Circle(r) => std::f64::consts::PI * r * r,
Shape::Rect { w, h } => w * h,
}
}
fn main() {
let p = Point { x: 3.0, y: 4.0 };
println!("{:?} {}", p, p.distance()); // Point { x: 3.0, y: 4.0 } 5
println!("{}", area(&Shape::Rect { w: 2.0, h: 3.0 })); // 6
let nums = [10, 20];
if let Some(v) = nums.first() {
println!("first is {v}"); // first is 10
}
}Trait mendefinisikan perilaku bersama, mirip interface: #[derive(Debug)] meminta compiler mengimplementasikan trait Debug agar {:?} bisa mencetak struct.
Collection: Vec dan HashMap
use std::collections::HashMap;
fn main() {
let mut nums = vec![3, 1, 2];
nums.push(5);
nums.sort();
println!("{:?} {}", nums, nums.len()); // [1, 2, 3, 5] 4
let evens: Vec<i32> = nums.iter().filter(|n| *n % 2 == 0).map(|n| n * n).collect();
println!("{:?}", evens); // [4]
let mut ages: HashMap<&str, u32> = HashMap::new();
ages.insert("ann", 31);
ages.insert("bob", 25);
*ages.entry("ann").or_insert(0) += 1;
println!("{:?} {:?}", ages.get("ann"), ages.get("zed")); // Some(32) None
}Iterator dengan closure (|n| ...) seperti filter, map, dan collect dikompilasi menjadi kode secepat perulangan yang ditulis tangan.
Penanganan error dengan Result dan ?
Fungsi yang bisa gagal mengembalikan Result<T, E>: Ok(value) atau Err(error). Operator ? mengembalikan error ke pemanggil lebih awal, sehingga jalur error tetap ringkas. Bug yang tak dapat dipulihkan memakai panic!.
use std::num::ParseIntError;
fn parse_age(text: &str) -> Result<u32, ParseIntError> {
let age: u32 = text.trim().parse()?; // returns Err early on bad input
Ok(age)
}
fn main() {
match parse_age("41") {
Ok(age) => println!("age {age}"), // age 41
Err(e) => println!("error: {e}"),
}
match parse_age("abc") {
Ok(age) => println!("age {age}"),
Err(e) => println!("error: {e}"), // error: invalid digit found in string
}
}Program kecil yang lengkap: frekuensi kata
Taruh ini di src/main.rs dan jalankan cargo run -- notes.txt.
use std::collections::HashMap;
use std::{env, fs, process};
fn main() {
let path = env::args().nth(1).unwrap_or_else(|| {
eprintln!("usage: words FILE");
process::exit(1);
});
let text = match fs::read_to_string(&path) {
Ok(t) => t.to_lowercase(),
Err(e) => {
eprintln!("cannot read {path}: {e}");
process::exit(1);
}
};
let mut counts: HashMap<&str, usize> = HashMap::new();
for word in text.split(|c: char| !c.is_alphanumeric()).filter(|w| !w.is_empty()) {
*counts.entry(word).or_insert(0) += 1;
}
let mut top: Vec<(&str, usize)> = 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(5) {
println!("{n:5} {word}");
}
}HashMap menyimpan slice &str yang meminjam dari text, jadi tidak ada kata yang disalin; borrow checker menjamin bahwa text hidup cukup lama.
Kesalahan umum pemula
- Memakai nilai setelah dipindahkan. Pinjam (
&value) atau clone dengan sengaja. - Mencoba mengindeks
Stringdengans[0]. String berformat UTF-8, jadi gunakans.chars()atau byte slice pada batas karakter. - Memegang reference ke
Vecsaat melakukan push; push dapat mengalokasikan ulang, sehingga compiler melarangnya. - Mencampuradukkan
String(owned) dan&str(borrowed). Terima&strpada parameter fungsi agar keduanya bisa dipakai.
Langkah selanjutnya
Baca "The Rust Programming Language" (dikenal sebagai the book), tersedia gratis di doc.rust-lang.org, dan coba Rust by Example untuk contoh singkat yang bisa dijalankan. Lalu pelajari trait dan generic secara mendalam, lifetime, pengujian dengan cargo test, dan crate dari crates.io seperti serde untuk serialisasi dan tokio untuk program async. Jalankan cargo clippy untuk saran lint tambahan.
FAQ
Apakah Rust sulit dipelajari?
Beberapa minggu pertama lebih sulit daripada di kebanyakan bahasa karena ownership dan borrowing adalah ide baru. Setelah terbiasa, compiler menangkap banyak bug lebih awal, dan banyak developer merasa kode Rust lebih mudah dirawat.
Apakah Rust punya garbage collector?
Tidak. Memori dibebaskan otomatis ketika owner-nya keluar dari scope, ditentukan saat kompilasi oleh aturan ownership. Tipe reference-counted Rc dan Arc tersedia untuk kepemilikan bersama.
Apa itu unsafe Rust?
Kode di dalam blok unsafe boleh melakukan hal yang tidak dapat diverifikasi compiler, seperti men-dereference raw pointer atau memanggil fungsi C. Pemakaiannya dibatasi, biasanya dibungkus dalam API yang aman.
Rust atau Go: mana yang sebaiknya dipelajari?
Go lebih sederhana dan lebih cepat dipelajari serta populer untuk layanan jaringan. Rust memberi kontrol dan performa lebih besar dengan jaminan yang lebih kuat, dengan konsekuensi kurva belajar yang lebih curam.