📌 Konsep Dasar Rust
Pengantar
- Rust adalah bahasa pemrograman sistem yang mengutamakan kecepatan, keamanan, dan kontrol atas memori.
- Menyediakan kekuatan low-level (seperti C/C++) dengan ergonomi high-level.
- Compiler Rust sangat ketat, membantu mencegah bug sejak awal.
Siapa yang Cocok Menggunakan Rust?
- Tim Developer: Mempermudah kolaborasi dengan sistem type dan error yang kuat.
- Mahasiswa & Pelajar: Komunitas terbuka dan mendukung pembelajaran sistem.
- Perusahaan: Digunakan di CLI tools, embedded, web service, machine learning, dsb.
- Open Source Developer: Banyak peluang kontribusi.
- Penggemar Kecepatan & Stabilitas: Fokus pada zero-cost abstraction.
🛠️ Instalasi dan Dasar Penggunaan
Instalasi Rust
- Gunakan
rustup
untuk instalasi dan manajemen versi. - Dilengkapi dengan dokumentasi lokal (
rustup doc
). - Alat penting:
-
rustc
: Compiler utama. -
cargo
: Package manager & build system.
-
Menulis Program Pertama: Hello, World!
- File:
main.rs
- Struktur dasar:
fn main() {
println!("Hello, world!");
}
- Kompilasi manual:
rustc main.rs
- Eksekusi:
./main
ataumain.exe
📦 Manajemen Proyek dengan Cargo
Apa Itu Cargo?
- Tools default untuk membuat, membangun, dan menjalankan proyek Rust.
- Mengelola dependensi (crates), build, dan distribusi.
Dasar-dasar Cargo
-
cargo new project_name
: Membuat proyek baru. -
cargo build
: Kompilasi. -
cargo run
: Kompilasi & eksekusi. -
cargo check
: Mengecek kesalahan tanpa build. -
cargo build --release
: Build dengan optimisasi.
Struktur Proyek Cargo
-
Cargo.toml
: Konfigurasi proyek dan dependensi. -
src/main.rs
: Entry point program.
🎮 Bab 2: Membuat Game "Guess the Number"
Tujuan Proyek
- Menebak angka acak antara 1-100.
- Program memberi tahu apakah tebakan terlalu tinggi, rendah, atau tepat.
Konsep yang Diperkenalkan
-
use std::io;
: Mengambil input dari pengguna. - Variabel dengan
let
danmut
. - String dan konversi ke angka (
parse::<u32>
). - Shadowing: Mendeklarasikan ulang variabel dengan nama yang sama.
-
rand
crate: Digunakan untuk menghasilkan angka acak. -
Ordering
danmatch
: Digunakan untuk membandingkan dan menangani hasil tebakan.
Contoh Match Expression
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
Error Handling
-
expect("error message")
: Menangani error pada operasi I/O atau parsing.
🚀 Floating Topics (Topik Tambahan)
Crate dan Dependency Management
- Semua crate eksternal dideklarasikan di
[dependencies]
dalamCargo.toml
. - Cargo akan membuat
Cargo.lock
untuk menjaga versi dependensi tetap konsisten.
Tools Pendukung
-
rustfmt
: Formatter otomatis untuk kode Rust. -
rust-analyzer
: IntelliSense & error hints di editor.
🧾 Variabel dan Mutabilitas
Variabel
- Dideklarasikan dengan
let
. - Secara default immutable (tidak bisa diubah).
- Gunakan
mut
untuk menjadikannya mutable.
let x = 5; // immutable
let mut y = 10; // mutable
Shadowing
- Variabel dapat dideklarasikan ulang dengan nama yang sama.
- Berguna untuk mengganti tipe data atau memperbarui nilai.
🔢 Tipe Data
Scalar Types
- Integer (
i32
,u32
, dll) - Floating-point (
f64
,f32
) - Boolean (
bool
) - Character (
char
)
Compound Types
- Tuple: Dapat menggabungkan beberapa tipe.
let tup: (i32, f64, u8) = (500, 6.4, 1);
- Array: Koleksi dengan ukuran tetap.
let a = [1, 2, 3, 4, 5];
🎯 Fungsi
- Didefinisikan dengan
fn
. - Bisa mengembalikan nilai dengan
-> T
danreturn
, atau langsung ekspresi di akhir blok.
fn add(x: i32, y: i32) -> i32 {
x + y
}
💬 Komentar
- Komentar satu baris:
// komentar
- Komentar dokumentasi:
///
(digunakan di crate atau API docs)
🔁 Kontrol Alur
if Expressions
- Dapat digunakan seperti ekspresi, bukan hanya statement.
let number = if condition { 5 } else { 6 };
loop
- Perulangan tak hingga sampai
break
.
while
- Perulangan dengan syarat.
for
- Paling idiomatik untuk iterasi koleksi.
for number in (1..4).rev() {
println!("{number}!");
}
🔐 Apa Itu Ownership?
Konsep Dasar
Ownership adalah sistem pengelolaan memori di Rust dengan aturan yang dicek oleh compiler:
- Setiap nilai memiliki satu "pemilik" (owner).
- Hanya satu owner yang aktif dalam satu waktu.
- Ketika owner keluar dari scope, nilai akan di-drop secara otomatis.
Keuntungan:
- Tidak ada garbage collector.
- Menghindari memory leak dan double free.
- Performa tinggi dan aman.
📦 Stack vs Heap
Stack
- Digunakan untuk data berukuran tetap.
- Akses cepat, LIFO (Last In First Out).
- Contoh: integer, float, boolean.
Heap
- Untuk data berukuran dinamis.
- Alokasi lebih lambat, tapi fleksibel.
- Contoh:
String
,Vec
.
🎁 Ownership pada Tipe Data
Tipe Copy vs Move
- Tipe sederhana (seperti
i32
) di-copy secara default. - Tipe kompleks (seperti
String
) di-move:- Setelah dipindahkan, variabel asal tidak bisa digunakan lagi.
let x = 5; // Copy
let y = x; // x masih bisa digunakan
let s1 = String::from("hello");
let s2 = s1; // s1 sudah tidak bisa digunakan
Clone
Gunakan .clone()
untuk menyalin data kompleks jika ingin kedua variabel tetap valid.
📤 Fungsi dan Ownership
- Passing ke fungsi memindahkan ownership (jika bukan tipe Copy).
- Fungsi dapat mengembalikan ownership agar nilai bisa digunakan kembali.
fn takes_ownership(s: String) { ... }
fn gives_ownership() -> String { ... }
🔄 Borrowing dan Referensi
Immutable Reference
- Gunakan
&
untuk meminjam nilai tanpa mengambil kepemilikan. - Hanya bisa memiliki satu atau lebih immutable borrow secara bersamaan.
fn calculate_length(s: &String) -> usize { s.len() }
Mutable Reference
- Gunakan
&mut
untuk referensi yang bisa diubah. - Hanya boleh ada satu mutable borrow dalam satu waktu.
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
⚠️ Tidak boleh campur
&
dan&mut
pada saat bersamaan karena akan menimbulkan race condition.
✂️ Slices
- Slice memungkinkan akses ke bagian dari koleksi tanpa mengambil ownership.
- Contoh:
&str
,&[T]
let s = String::from("hello world");
let hello = &s[0..5]; // slice ke "hello"
String Slice
- Umum digunakan untuk parameter fungsi yang menerima
&str
.
💡 Kesimpulan
Ownership memungkinkan Rust:
- Mengelola memori tanpa runtime GC.
- Mencegah bug umum seperti use-after-free dan race condition.
- Mengutamakan keamanan dan efisiensi.
Struct, yaitu tipe data yang memungkinkan pengelompokan data secara terstruktur dan bermakna. Struct juga membuka pintu bagi pendekatan object-oriented melalui method dan fungsi terkait.
🧱 Definisi dan Pembuatan Struct
Struct Biasa
- Struct didefinisikan dengan keyword
struct
, diikuti nama, dan daftar field. - Field memiliki nama dan tipe.
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
Instansiasi Struct
- Gunakan
{}
dengan pasanganfield: value
.
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someuser"),
active: true,
sign_in_count: 1,
};
Struct Update Syntax
- Gunakan
..
untuk menyalin nilai dari struct lain.
let user2 = User {
email: String::from("another@example.com"),
..user1
};
⚠️ Perlu diperhatikan: field yang bertipe
String
akan moved jika dipakai dalam..user1
.
🧩 Jenis Struct Lain
Tuple Struct
- Seperti tuple, tapi dengan nama struct sebagai pembeda tipe.
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
Unit-like Struct
- Struct tanpa field, berguna untuk trait atau marker.
struct AlwaysEqual;
📐 Method dan impl
Block impl
- Tempat mendefinisikan method dan fungsi terkait (associated functions).
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
Method vs Associated Function
- Method: memiliki parameter
&self
. - Associated function: tidak memiliki
self
, dipanggil dengan::
, seperti constructor.
impl Rectangle {
fn square(size: u32) -> Self {
Self {
width: size,
height: size,
}
}
}
📏 Studi Kasus: Menghitung Luas Persegi Panjang
Refactor Kode
- Dari parameter longgar → tuple → struct.
- Struct membuat kode lebih bermakna dan eksplisit.
struct Rectangle {
width: u32,
height: u32,
}
💡 Kesimpulan
- Struct adalah cara idiomatik Rust untuk membuat tipe kustom.
- Memberikan konteks dan keterbacaan lebih baik daripada tuple.
- Melalui
impl
, kita bisa menambahkan perilaku (behavior) ke struct.
enum, salah satu fitur paling ekspresif di Rust, serta pattern matching menggunakan match
dan if let
.
🎲 Apa Itu Enum?
Definisi
- Enum (enumeration) adalah tipe data yang bisa memiliki beberapa variant.
- Setiap variant bisa menyimpan data berbeda.
enum IpAddrKind {
V4,
V6,
}
Enum dengan Data
- Variant bisa membawa data, mirip struct atau tuple.
enum IpAddr {
V4(String),
V6(String),
}
Enum vs Struct
- Gunakan struct jika semua data diperlukan bersamaan.
- Gunakan enum jika hanya satu dari sekian kemungkinan yang relevan dalam satu waktu.
💡 Contoh Kasus: IP Address
- Struct alternatif (lebih verbose):
struct IpAddr {
kind: IpAddrKind,
address: String,
}
- Enum lebih ringkas:
enum IpAddr {
V4(String),
V6(String),
}
✨ Pattern Matching dengan match
Dasar Penggunaan
- Digunakan untuk mencocokkan nilai dengan pola tertentu.
- Menjamin semua kasus ditangani (exhaustive).
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
match
dengan Enum
- Dapat mengambil dan menggunakan data dalam variant.
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
}
match msg {
Message::Move { x, y } => println!("Move to {x}, {y}"),
_ => (),
}
📦 Enum Option<T>
- Mengganti penggunaan null (tidak ada nilai).
- Bentuk:
-
Some(T)
jika ada nilai. -
None
jika tidak ada nilai.
-
let some_number = Some(5);
let absent_number: Option<i32> = None;
Pattern Matching dengan Option
match x {
Some(i) => Some(i + 1),
None => None,
}
- Rust memaksa penanganan semua kemungkinan (
Some
danNone
) untuk mencegah bug.
🧠 Idiomatik: if let
- Sintaks ringkas untuk menangani satu pola.
if let Some(3) = some_value {
println!("Three!");
}
💡 Kesimpulan
- Enum membantu representasi logika program yang kompleks dan bervariasi.
- Digabungkan dengan
match
, memberikan kontrol alur yang powerful dan aman. -
Option<T>
adalah contoh enum yang banyak digunakan dalam Rust untuk menangani ketidakpastian nilai tanpa null.
Part ini menjelaskan bagaimana Rust mengorganisasi program dalam skala besar, termasuk struktur proyek, pemisahan kode, dan kontrol visibilitas.
📦 Package dan Crate
Crate
- Unit kompilasi terkecil di Rust.
- Bisa berupa:
-
Binary crate: memiliki fungsi
main()
, menghasilkan executable. -
Library crate: tidak memiliki
main()
, digunakan sebagai pustaka.
-
Binary crate: memiliki fungsi
Package
- Kumpulan dari satu atau lebih crate.
- Harus memiliki minimal satu crate (binary atau library).
- Didefinisikan melalui
Cargo.toml
.
Konvensi Cargo
-
src/main.rs
: root dari binary crate. -
src/lib.rs
: root dari library crate. - Bisa memiliki banyak binary crate di
src/bin/
.
🧭 Module
Tujuan Module
- Mengorganisasi kode menjadi bagian-bagian yang reusable dan terpisah.
- Mencegah konflik nama dan meningkatkan keterbacaan.
Cara Mendeklarasikan Module
-
mod nama_module;
di file utama (main.rs
ataulib.rs
). - File atau folder yang sesuai harus tersedia:
-
mod garden;
→ akan cari digarden.rs
ataugarden/mod.rs
.
-
Submodule
- Bisa didefinisikan di dalam file module utama, atau dalam file tersendiri di dalam folder module.
🔐 Kontrol Visibilitas
- Default: item bersifat privat dalam module.
- Gunakan
pub
untuk membuat item publik (dapat diakses dari luar module).
pub fn add_to_waitlist() {}
- Gunakan
use
untuk mengimpor path agar lebih singkat.
use crate::front_of_house::hosting;
hosting::add_to_waitlist();
📚 Paths dalam Module
Absolute Path
- Dimulai dari crate root.
crate::front_of_house::hosting::add_to_waitlist();
Relative Path
- Dimulai dari module saat ini menggunakan
self
,super
, atau nama module langsung.
✂️ Pemisahan File
- Structurisasi kode modular dengan memecah file besar.
- Setiap
mod
bisa dideklarasikan ke file terpisah.
mod front_of_house; // front_of_house.rs
mod back_of_house; // back_of_house.rs
💡 Kesimpulan
- Packages dan crates mengelola distribusi dan kompilasi kode.
- Modules membantu pemisahan logika dan kontrol akses.
- Fitur
use
,mod
,pub
, dan path sangat penting untuk proyek skala besar.
Part ini membahas tiga struktur data penting yang disediakan oleh pustaka standar Rust: Vec
, String
, dan HashMap
.
📚 Vector (Vec<T>
)
Definisi
- Struktur data dinamis untuk menyimpan elemen bertipe sama.
- Disimpan berdampingan dalam memori (heap).
Dasar Penggunaan
let v: Vec<i32> = Vec::new();
let v = vec![1, 2, 3]; // makro literal
Akses dan Iterasi
- Gunakan indeks:
v[0]
, atau.get(index)
untuk hasilOption
. - Iterasi menggunakan
for
:
for i in &v { println!("{i}"); }
Mutasi
- Dapat dimutasi menggunakan
.push()
. - Borrow checker mencegah kombinasi pinjam immutable dan mutable secara bersamaan.
🧵 String (String
)
Tipe dan Representasi
-
String
adalah koleksi karakter berbasis UTF-8 yang dapat dimodifikasi. -
&str
adalah string slice (biasanya literal string).
Pembuatan dan Manipulasi
let mut s = String::from("Hello");
s.push_str(", world!");
Indexing
- Tidak bisa diindeks seperti array karena karakter UTF-8 bisa lebih dari 1 byte.
Iterasi
-
.chars()
untuk karakter -
.bytes()
untuk byte
for c in "Зд".chars() { println!("{c}"); }
🗺️ Hash Map (HashMap<K, V>
)
Definisi
- Koleksi pasangan kunci-nilai, seperti dictionary di bahasa lain.
Dasar Penggunaan
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
Akses dan Iterasi
- Gunakan
.get()
untuk mengakses nilai. - Iterasi dengan
for (key, value) in &hash_map
.
Ownership
- Key dan value bisa move ke dalam map.
- Tipe
Copy
disalin,String
dipindahkan.
⚠️ Borrowing dan Koleksi
- Koleksi berbasis heap bisa mengubah lokasi memori saat mutasi (contoh:
.push()
). - Rust mencegah penggunaan referensi setelah potensi perubahan memori.
let first = &v[0];
v.push(6); // error: borrow conflict
💡 Kesimpulan
- Koleksi di Rust mengutamakan efisiensi dan keamanan memori.
-
Vec
untuk daftar nilai,String
untuk teks,HashMap
untuk asosiatif. - Semua koleksi mengikuti aturan ownership dan borrowing yang ketat.
Rust membedakan dua jenis error:
- Unrecoverable Errors: Tidak bisa diperbaiki, program akan dihentikan.
- Recoverable Errors: Bisa ditangani dan program bisa lanjut.
Rust tidak menggunakan exceptions, tetapi memakai enum Result<T, E>
dan makro panic!
.
💥 Unrecoverable Errors dengan panic!
Penggunaan
- Untuk error serius yang tidak dapat diantisipasi.
- Akan menghentikan eksekusi program dan mencetak pesan error.
panic!("Something went wrong!");
Default Behavior
- Rust melakukan unwinding: membersihkan stack.
- Bisa diubah menjadi abort untuk binary lebih kecil:
[profile.release]
panic = 'abort'
✅ Recoverable Errors dengan Result<T, E>
Struktur
enum Result<T, E> {
Ok(T),
Err(E),
}
-
Ok(T)
→ Operasi sukses. -
Err(E)
→ Operasi gagal dengan alasan tertentu.
Contoh Penggunaan
use std::fs::File;
let f = File::open("hello.txt");
match f {
Ok(file) => ...,
Err(error) => ...,
}
Pattern Matching
Gunakan match
atau if let
untuk menangani Result
.
if let Ok(file) = File::open("hello.txt") {
// gunakan file
}
❓ Operator ?
untuk Propagasi Error
Fungsi
- Mempermudah propagasi error ke pemanggil fungsi.
- Jika hasil
Result
adalahErr
, maka akan langsung return dari fungsi.
fn read_file() -> Result<String, std::io::Error> {
let mut f = File::open("hello.txt")?;
// ...
Ok(contents)
}
🔧 Menentukan Strategi Penanganan Error
Gunakan panic!
jika:
- Error adalah bug (contoh: indeks di luar batas array).
- Program tidak bisa lanjut.
Gunakan Result
jika:
- Error bisa terjadi karena kondisi eksternal (file tidak ditemukan, koneksi gagal).
- Program bisa mengambil tindakan alternatif.
📌 Kesimpulan
- Rust mengharuskan kita untuk menyadari dan menangani error.
-
panic!
untuk error fatal,Result<T, E>
untuk error yang dapat ditangani. - Operator
?
menyederhanakan penanganan error yang dibungkus dalamResult
.
Part ini memperkenalkan tiga fitur powerful untuk menulis kode fleksibel dan reusable di Rust: Generics, Traits, dan Lifetimes.
🔁 Generics
Apa Itu Generic?
- Generic adalah placeholder tipe yang memungkinkan fungsi, struct, enum, dan method digunakan dengan berbagai tipe tanpa mengulang kode.
fn largest<T: PartialOrd>(list: &[T]) -> &T {
// ...
}
Penggunaan Generic
- Fungsi: Gunakan
T
sebagai pengganti tipe konkret. - Struct:
struct Point<T> {
x: T,
y: T,
}
- Enum:
enum Option<T> {
Some(T),
None,
}
Monomorphization
- Rust akan mengubah generic menjadi tipe konkret saat kompilasi → tidak ada cost runtime.
📦 Traits
Definisi Trait
- Trait adalah kumpulan method yang dapat diimplementasikan oleh tipe.
- Mirip konsep interface di bahasa lain.
pub trait Summary {
fn summarize(&self) -> String;
}
Trait Bound
- Digunakan untuk membatasi generic hanya ke tipe yang mengimplementasikan trait tertentu.
fn notify<T: Summary>(item: T) {
println!("Breaking news! {}", item.summarize());
}
- Bisa juga menggunakan
where
untuk memperjelas kode panjang.
🧬 Lifetimes
Apa Itu Lifetime?
- Lifetime adalah anotasi pada referensi untuk memastikan referensi tetap valid.
- Tujuan utama: mencegah dangling references.
Contoh Sederhana
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
// mengembalikan referensi yang berlaku selama lifetime 'a
}
Lifetime Elision
Rust sering bisa menyimpulkan lifetime secara otomatis. Tapi saat kompleksitas meningkat, kita perlu menuliskannya secara eksplisit.
'static
Lifetime
- Menunjukkan bahwa referensi berlaku sepanjang umur program.
- Semua string literal memiliki lifetime
'static
.
🔗 Kombinasi: Generics + Trait Bounds + Lifetimes
fn announce_and_return_part<'a, T>(
announcement: T,
s: &'a str,
) -> &'a str
where
T: Display,
{
println!("Announcement: {announcement}");
s
}
💡 Kesimpulan
- Generics: Kode lebih fleksibel dan reusable.
- Traits: Membuat abstraksi perilaku dan batasan tipe.
- Lifetimes: Mencegah kesalahan referensi memori secara aman.
Semua fitur ini dicek di waktu kompilasi, sehingga aman dan tetap efisien.
Part ini membahas bagaimana menulis dan menjalankan unit tests dan integration tests di Rust. Tujuannya adalah memastikan bahwa program berjalan sesuai harapan dan tetap benar setelah perubahan kode.
🧪 Struktur Dasar Test
Fungsi Test
- Test adalah fungsi biasa dengan atribut
#[test]
. - Dijalankan menggunakan perintah:
cargo test
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
Tiga Langkah Umum Test
- Setup data atau state.
- Jalankan kode yang ingin diuji.
- Gunakan assertion untuk memastikan hasil sesuai ekspektasi.
✅ Macro untuk Assertion
assert!(condition)
assert_eq!(left, right)
assert_ne!(left, right)
Jika assertion gagal, panic!
akan terjadi dan test akan gagal.
🧪 Unit Test
Karakteristik:
- Diletakkan dalam file yang sama dengan kode (
src/
). - Di dalam module
#[cfg(test)]
. - Dapat mengakses item private.
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn internal() {
let result = internal_adder(2, 2);
assert_eq!(result, 4);
}
}
🔗 Integration Test
Karakteristik:
- Diletakkan di folder
tests/
di luarsrc/
. - Diperlakukan sebagai crate terpisah.
- Hanya bisa mengakses public API.
// tests/integration_test.rs
use adder::add_two;
#[test]
fn it_adds_two() {
let result = add_two(2);
assert_eq!(result, 4);
}
⚙️ Pengaturan Tambahan
-
#[should_panic]
: Menandai bahwa test seharusnya gagal. -
#[ignore]
: Melewatkan test secara default, bisa dijalankan dengancargo test -- --ignored
.
#[test]
#[should_panic(expected = "panic message")]
fn it_panics() {
panic!("panic message");
}
📚 Dokumentasi dan Test
- Komentar dokumentasi (
///
) bisa berisi contoh kode. -
cargo test
akan menjalankan contoh di dokumen sebagai test (doc-tests).
/// # Examples
/// ```
{% endraw %}
/// assert_eq!(add(2, 2), 4);
///
{% raw %}
---
## 🧠 Kesimpulan
- Rust mendukung testing terstruktur dengan `#[test]` dan `cargo test`.
- Unit test dan integration test membantu memverifikasi baik fungsi tunggal maupun interaksi antarbagian.
- Tools dan makro test-nya efisien, aman, dan mudah digunakan untuk pengembangan yang andal.
---
Part ini menerapkan berbagai konsep Rust melalui proyek nyata: membuat program CLI yang mencari string dalam file, mirip dengan alat UNIX `grep`.
---
## 🎯 Tujuan Proyek
- Membangun tool CLI `minigrep`.
- Input: 2 argumen command line (`search term` dan `file path`).
- Output: Menampilkan baris dalam file yang mengandung `search term`.
---
## 🧱 Struktur Proyek
### File dan Module
- `src/main.rs`: entry point, bertugas parsing argumen dan memanggil logika dari `lib.rs`.
- `src/lib.rs`: berisi fungsi utama program dan logic `search`.
### Konsep yang Dipakai
- Argument parsing via `std::env::args`.
- Error handling dengan `Result`.
- File I/O dengan `std::fs`.
- Unit testing.
- Environment variable (`CASE_INSENSITIVE`).
- Penulisan ke `stderr` untuk pesan error.
---
## ⚙️ Tahapan Implementasi
### 1. Membaca Argument CLI
```rust
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
dbg!(args);
}
2. Struktur Config
- Memisahkan parsing ke dalam struct
Config
. - Fungsi
Config::build
mengembalikanResult<Config, &str>
.
struct Config {
query: String,
file_path: String,
}
3. Membaca File dan Menjalankan Pencarian
pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
let contents = fs::read_to_string(config.file_path)?;
for line in search(&config.query, &contents) {
println!("{line}");
}
Ok(())
}
4. Fungsi search
- Menerima query dan konten, mengembalikan vector string yang cocok.
fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
contents.lines().filter(|line| line.contains(query)).collect()
}
🧪 Menambahkan Test
#[test]
fn case_sensitive() {
let query = "duct";
let contents = "\
Rust:
safe, fast, productive.
Pick three.";
assert_eq!(vec!["safe, fast, productive."], search(query, contents));
}
🌐 Environment Variable untuk Case-Insensitive
- Jika
CASE_INSENSITIVE
diset, program akan menggunakan pencarian yang tidak sensitif terhadap kapital.
env::var("CASE_INSENSITIVE").is_ok()
🖨️ Output ke stderr
- Gunakan
eprintln!()
untuk error message → memungkinkan output utama bisa diarahkan ke file (> output.txt
), sedangkan error tetap tampil di terminal.
💡 Kesimpulan
- Proyek
minigrep
adalah penerapan nyata konsep Rust:- Argument parsing
- Error handling
- Modularisasi kode
- Testing
- File I/O
- Trait, lifetime, dan closure dasar
- Memberi gambaran bagaimana membangun CLI tool profesional.
Part ini membahas fitur Rust yang terinspirasi dari paradigma fungsional, seperti closures dan iterators, yang menawarkan cara ekspresif namun efisien untuk memproses data.
🔒 Closures
Definisi
- Closure adalah fungsi anonim yang bisa menangkap lingkungan tempat ia dibuat.
- Sintaks umum:
let add = |x, y| x + y;
Capturing Environment
Closures bisa menangkap nilai dengan:
- Borrow immutable (
&T
) - Borrow mutable (
&mut T
) - Ownership (
T
)
Pemilihan mekanisme tergantung dari isi closure.
Perbedaan dengan Fungsi Biasa
- Fungsi: eksplisit dalam parameter.
- Closure: bisa menangkap variabel dari lingkungan.
let color = String::from("red");
let print_color = || println!("Color: {}", color);
🔁 Iterators
Definisi
-
Iterator adalah trait dengan satu method wajib:
next()
, yang mengembalikanOption<T>
.
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
Cara Kerja
- Lazy (malas): tidak melakukan apapun sampai dikonsumsi.
- Dapat digunakan dengan
for
loop.
Konsumsi dan Adaptasi
-
Consuming adapters:
.sum()
,.collect()
, dsb → memakan iterator. -
Iterator adapters:
.map()
,.filter()
→ membentuk iterator baru.
let v = vec![1, 2, 3];
let squared: Vec<_> = v.iter().map(|x| x * x).collect();
🧪 Closures dalam Iterators
- Closure sering digunakan sebagai parameter ke method seperti
.map()
,.filter()
. - Closure bisa menangkap variabel di luar sebagai kriteria logika.
let shoe_size = 10;
shoes.into_iter().filter(|s| s.size == shoe_size).collect();
⚡ Kinerja
- Rust menerapkan zero-cost abstractions:
- Closures dan iterator tidak memperlambat performa runtime.
- Kompilasi menghasilkan kode setara atau bahkan lebih cepat dari manual loop.
💡 Kesimpulan
- Closures memungkinkan fungsi fleksibel dan reusable.
- Iterators memberikan cara aman dan efisien untuk memproses koleksi data.
- Kombinasi keduanya menciptakan gaya pemrograman deklaratif dan bersih—tanpa mengorbankan performa.
Part ini membahas cara memublikasikan crate buatan kita ke komunitas Rust melalui crates.io, serta bagaimana menyusun crate agar lebih mudah digunakan dan didokumentasikan.
📦 Menyiapkan Crate untuk Publikasi
Persiapan Dasar
- Pastikan
Cargo.toml
memiliki metadata:-
name
,version
,description
,license
-
- Tanpa metadata ini, publikasi akan gagal.
[package]
name = "guessing_game"
version = "0.1.0"
edition = "2021"
description = "A fun game where you guess a number"
license = "MIT OR Apache-2.0"
Akun crates.io
- Login ke crates.io dengan akun GitHub.
- Ambil token API dari crates.io/me.
- Jalankan:
cargo login <token>
🚀 Publikasi ke crates.io
Langkah-langkah
- Cek crate unik (nama belum digunakan).
- Tambahkan metadata.
- Jalankan:
cargo publish
⚠️ Versi yang sudah dipublikasikan tidak bisa dihapus. crates.io adalah arsip permanen.
Update Versi
- Gunakan semantic versioning (
1.0.0
,1.0.1
,2.0.0
, dst). - Untuk update: ubah versi →
cargo publish
ulang.
🔄 Menarik Versi Lama (Yanking)
- Tidak bisa menghapus versi crate, tapi bisa di-yank agar tidak bisa dipakai proyek baru.
cargo yank --vers 1.0.1
cargo yank --vers 1.0.1 --undo
- Berguna untuk menarik versi yang rusak, tanpa mengganggu proyek yang sudah menggunakannya.
📚 Dokumentasi Crate
Komentar Dokumentasi
- Gunakan
///
sebelum item publik. - Bisa menyertakan contoh kode dengan Markdown.
/// Adds one to the number.
/// # Examples
/// ```
{% endraw %}
/// let x = 5;
/// assert_eq!(6, my_crate::add_one(x));
///
{% raw %}
pub fn add_one(x: i32) -> i32 {
x + 1
}
- Jalankan `cargo doc` untuk menghasilkan HTML dokumentasi.
- Gunakan `cargo doc --open` untuk langsung membuka di browser.
---
## 🔄 Re-export API dengan `pub use`
- Untuk menyederhanakan struktur public API.
- Menyembunyikan struktur internal crate.
```rust
pub use self::utils::mix;
💡 Kesimpulan
- Cargo memudahkan publikasi dan manajemen distribusi crate.
- Dokumentasi dan struktur API yang baik meningkatkan kegunaan crate di komunitas.
- crates.io adalah pusat ekosistem Rust, dan partisipasi aktif sangat dianjurkan.
Part ini memperkenalkan smart pointers, yaitu tipe data dengan kemampuan tambahan selain menyimpan alamat memori, seperti pengelolaan kepemilikan, penghitungan referensi, atau kontrol mutabilitas.
🧭 Apa Itu Smart Pointer?
Perbedaan dengan Referensi Biasa
- Referensi (
&T
) hanya meminjam data, tidak memiliki. - Smart pointer umumnya memiliki data dan menyimpan metadata tambahan.
Ciri Umum Smart Pointer:
- Diimplementasikan sebagai
struct
. - Biasanya mengimplementasikan trait:
-
Deref
: agar bisa diperlakukan seperti referensi. -
Drop
: untuk mengeksekusi kode ketika keluar scope.
-
📦 Box
Fungsi
- Menyimpan data di heap.
- Digunakan saat:
- Ukuran tipe tidak diketahui di compile time.
- Data besar yang ingin dipindah tanpa disalin.
- Trait object (untuk dynamic dispatch).
let b = Box::new(5);
👥 Rc - Reference Counted
Tujuan
- Mendukung multiple ownership.
- Cocok untuk struktur data seperti graf atau pohon dengan banyak referensi ke satu node.
use std::rc::Rc;
let a = Rc::new(5);
let b = Rc::clone(&a);
Fitur
- Menjaga hitungan pemilik aktif.
- Ketika count = 0, data dibersihkan otomatis.
⚠️ Tidak thread-safe — gunakan
Arc<T>
jika diperlukan di thread berbeda.
🌀 RefCell dan Interior Mutability
Apa Itu Interior Mutability?
- Memungkinkan data diubah walau berada di konteks immutable.
RefCell
- Menyediakan mutasi runtime, bukan compile time seperti
&mut
. - Panic saat aturan borrow dilanggar — dicek saat program berjalan.
use std::cell::RefCell;
let data = RefCell::new(5);
*data.borrow_mut() += 1;
🎭 Trait Deref
- Mengizinkan smart pointer digunakan layaknya referensi biasa (
*my_box
). - Mendukung deref coercion, yaitu otomatis konversi
&Box<T>
→&T
.
💣 Trait Drop
- Menentukan aksi saat smart pointer keluar scope.
- Misalnya untuk logging atau membebaskan sumber daya.
impl Drop for MyPointer {
fn drop(&mut self) {
println!("Cleaning up!");
}
}
🔁 Weak dan Siklus Referensi
-
Rc<T>
bisa menyebabkan reference cycle → memory leak. - Gunakan
Weak<T>
agar tidak ikut menaikkan hitungan pemilik.
use std::rc::Weak;
💡 Kesimpulan
- Smart pointer memberi kontrol memori tambahan:
-
Box<T>
untuk heap. -
Rc<T>
untuk banyak pemilik. -
RefCell<T>
untuk runtime borrow check.
-
- Kombinasi trait
Deref
danDrop
memberikan fleksibilitas dan keamanan. - Penting untuk memahami kapan harus menggunakan pointer biasa, smart pointer, atau keduanya.
Part ini membahas bagaimana Rust memungkinkan pemrograman serempak (multithreading, messaging, shared state) secara aman dan efisien, tanpa race condition atau undefined behavior.
🔀 Thread
Dasar Penggunaan
- Gunakan
std::thread::spawn
untuk menjalankan fungsi di thread terpisah.
use std::thread;
thread::spawn(|| {
println!("Hello from another thread!");
});
Join
- Untuk menunggu thread selesai, gunakan
.join()
.
let handle = thread::spawn(|| { ... });
handle.join().unwrap();
Ownership dan move
- Closure thread harus mengambil kepemilikan (
move
) jika menggunakan data luar scope-nya.
let v = vec![1, 2, 3];
let handle = thread::spawn(move || {
println!("{:?}", v);
});
📡 Message Passing
Channel
- Gunakan
std::sync::mpsc
untuk komunikasi antar-thread. -
mpsc
= multiple producer, single consumer.
let (tx, rx) = mpsc::channel();
tx.send(String::from("hi")).unwrap();
let received = rx.recv().unwrap();
Cloning Sender
- Bisa membuat banyak pengirim (
tx
) dengantx.clone()
.
🔄 Shared-State Concurrency
Mutex<T>
- Mengelola akses eksklusif ke data bersama (mutual exclusion).
- Gunakan
.lock()
untuk mengakses data.
use std::sync::Mutex;
let m = Mutex::new(5);
{
let mut num = m.lock().unwrap();
*num += 1;
}
Arc<T>
(Atomic Reference Counted)
- Digunakan agar
Mutex<T>
bisa dibagikan ke beberapa thread.
use std::sync::{Arc, Mutex};
use std::thread;
let counter = Arc::new(Mutex::new(0));
💡 Konsep Tambahan
Fearless Concurrency
- Rust menghilangkan race condition dengan aturan borrow checker dan type system.
- Banyak error concurrency terdeteksi saat compile-time, bukan runtime.
🧠 Kesimpulan
- Rust memberikan alat concurrency yang aman secara default.
- Pilihan utama:
-
thread::spawn
untuk multithreading dasar. -
mpsc
untuk message passing. -
Mutex<T>
danArc<T>
untuk shared state.
-
- Tidak seperti bahasa lain, Rust memastikan keamanan memori dan thread tanpa garbage collector.
Part ini menjelaskan bagaimana Rust mendukung pemrograman berorientasi objek (Object-Oriented Programming / OOP) dengan pendekatan yang berbeda, yaitu menggunakan trait object sebagai pengganti inheritance.
🧱 OOP dalam Konteks Rust
Karakteristik OOP Umum
- Objects: Kombinasi data dan fungsi.
- Encapsulation: Menyembunyikan detail implementasi.
- Inheritance: Pewarisan antar tipe.
- Polymorphism: Kemampuan tipe berbeda bertindak serupa.
Rust mendukung object dan encapsulation, namun tidak memiliki inheritance secara langsung.
🎭 Trait sebagai Pengganti Inheritance
Trait Object
- Trait object memungkinkan pemanggilan method pada berbagai tipe secara dinamis selama mereka mengimplementasikan trait tertentu.
- Disusun menggunakan
Box<dyn Trait>
.
pub trait Draw {
fn draw(&self);
}
pub struct Screen {
pub components: Vec<Box<dyn Draw>>,
}
Implementasi
- Tipe-tipe konkret (
Button
,SelectBox
) bisa di-wrap dalam trait object dan disimpan dalam vektor.
struct Button { ... }
impl Draw for Button {
fn draw(&self) { ... }
}
🔄 Polymorphism di Rust
Static vs Dynamic Dispatch
- Static: Menggunakan generic + trait bound. Diketahui saat compile time.
-
Dynamic: Menggunakan trait object (
dyn Trait
). Ditentukan saat runtime.
Trait object digunakan saat tipe spesifik tidak diketahui saat kompilasi.
✨ Contoh: GUI Abstraksi
Rust dapat membuat antarmuka seperti framework GUI:
- Tipe-tipe berbeda (
Button
,TextField
, dll.) di-wrap dalamBox<dyn Draw>
. - Disimpan dalam koleksi dan semuanya bisa dipanggil
.draw()
.
for component in screen.components.iter() {
component.draw();
}
💡 Kesimpulan
- Rust bukan bahasa OOP murni, tapi mendukung prinsip OOP secara idiomatik.
- Trait dan trait object adalah cara utama mengimplementasikan polymorphism di Rust.
- Tidak ada inheritance, namun dapat dicapai melalui:
- Trait dengan implementasi default.
- Komposisi dan trait bound.
- Pendekatan Rust mendorong desain kode yang eksplisit, aman, dan modular.
Part ini adalah kulminasi dari perjalanan belajar Rust. Di sini, kita membuat web server multithreaded dari nol sebagai proyek praktis untuk menerapkan konsep-konsep penting yang telah dipelajari.
🌐 Tujuan Proyek
- Membangun server HTTP sederhana:
- Menerima koneksi TCP.
- Membaca dan mem-parsing request HTTP.
- Mengirimkan response sederhana.
- Menggunakan thread pool untuk menangani permintaan secara paralel.
🔌 Dasar-Dasar TCP & HTTP
TCP (Transmission Control Protocol)
- Menyediakan koneksi dua arah antara client-server.
- Rust menggunakan
std::net::TcpListener
untuk membuka socket.
HTTP
- Berbasis teks, menggunakan protokol request-response.
- Request umum:
GET / HTTP/1.1
🧪 Tahapan Implementasi
1. Mendengarkan Koneksi
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
2. Menangani Satu Request
- Membaca
TcpStream
. - Mengambil baris pertama dari HTTP request.
- Merespons hanya jika
GET /
, kirimkan file HTML.
let status_line = "HTTP/1.1 200 OK";
let contents = fs::read_to_string("hello.html").unwrap();
🧵 Membuat Thread Pool
Konsep
- Gunakan fixed-size thread pool untuk efisiensi.
- Kirim pekerjaan (
Job
) ke channel, pekerja akan mengeksekusinya.
Struktur
-
ThreadPool
: menyimpan thread dan pengirim (Sender<Job>
). -
Worker
: thread yang mengambil dan menjalankanJob
dari channel.
🧹 Graceful Shutdown
- Implementasi trait
Drop
untukThreadPool
. - Ketika server dimatikan, worker akan diberi sinyal untuk berhenti dan
join()
dipanggil agar thread selesai dengan bersih.
🚫 Bukan Production-Ready
Proyek ini bukan web server siap produksi, tapi:
- Memberikan pemahaman langsung akan dasar HTTP.
- Memberi gambaran bagaimana runtime async atau framework seperti
hyper
bekerja di balik layar.
💡 Kesimpulan
- Web server ini melibatkan:
- TCP, HTTP, I/O
- Threads, Channels, Mutex, Arc
- Ownership dan borrowing secara aman
- Proyek ini menunjukkan bahwa Rust ideal untuk low-level system programming, bahkan untuk membangun tool tingkat tinggi seperti server.
🧩 Lampiran (Appendix)
Lampiran dalam buku ini memberikan referensi singkat dan tambahan penting untuk memperdalam pemahaman konsep-konsep Rust.
📄 Appendix B – Operator dan Simbol
Berisi daftar lengkap operator dan simbol dalam Rust, termasuk konteks penggunaannya:
Beberapa contoh:
-
!
: macro expansion atau negasi -
==
,!=
: perbandingan nilai (PartialEq
) -
&
,&mut
: reference (borrow) -
::
: akses namespace/module -
<T>
: parameter generic -
#[attr]
,#![attr]
: atribut metadata -
...
,..=
,..
: pola range (eksklusif/inklusif)
🔧 Appendix C – Trait yang Bisa Di-derive
Rust menyediakan trait tertentu yang bisa di-derive otomatis oleh compiler:
Beberapa trait penting:
-
Debug
: untuk debugging via{:?}
-
PartialEq
,Eq
: untuk perbandingan nilai -
Clone
,Copy
: untuk duplikasi nilai -
Default
: menyediakan nilai default -
Hash
: memungkinkan objek dimasukkan keHashMap
-
Ord
,PartialOrd
: untuk perbandingan dan pengurutan
🗂️ Appendix D – Atribut Crate
Atribut khusus crate yang bisa digunakan untuk mengatur perilaku compile:
Contoh:
#![allow(unused)]
#![deny(warnings)]
🔤 Appendix E – Keyword yang Dicadangkan
Daftar kata kunci yang reserved untuk masa depan, meskipun belum digunakan sekarang:
Contoh:
-
abstract
,final
,macro
,virtual
,typeof
📚 Appendix F – Tooling
Daftar dan penjelasan alat-alat ekosistem Rust:
Tool penting:
-
cargo
– manajer proyek & build -
rustc
– compiler -
rustdoc
– generator dokumentasi -
clippy
– linting tool -
rustfmt
– formatting otomatis -
rustup
– installer & pengelola versi Rust
🧠 Kesimpulan Umum Lampiran
- Lampiran bukan bagian "wajib baca", tetapi sangat berguna sebagai referensi cepat.
- Sangat membantu saat butuh penjelasan singkat tentang sintaks, tooling, atau kebiasaan idiomatik Rust.
Top comments (0)