DEV Community

Cover image for The Rust Programming Language bahasa indonesia
Drian
Drian

Posted on

The Rust Programming Language bahasa indonesia

📌 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!");
  }
Enter fullscreen mode Exit fullscreen mode
  • Kompilasi manual: rustc main.rs
  • Eksekusi: ./main atau main.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 dan mut.
  • String dan konversi ke angka (parse::<u32>).
  • Shadowing: Mendeklarasikan ulang variabel dengan nama yang sama.
  • rand crate: Digunakan untuk menghasilkan angka acak.
  • Ordering dan match: 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!"),
}
Enter fullscreen mode Exit fullscreen mode

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] dalam Cargo.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
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode
  • Array: Koleksi dengan ukuran tetap.
  let a = [1, 2, 3, 4, 5];
Enter fullscreen mode Exit fullscreen mode

🎯 Fungsi

  • Didefinisikan dengan fn.
  • Bisa mengembalikan nilai dengan -> T dan return, atau langsung ekspresi di akhir blok.
fn add(x: i32, y: i32) -> i32 {
    x + y
}
Enter fullscreen mode Exit fullscreen mode

💬 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 };
Enter fullscreen mode Exit fullscreen mode

loop

  • Perulangan tak hingga sampai break.

while

  • Perulangan dengan syarat.

for

  • Paling idiomatik untuk iterasi koleksi.
for number in (1..4).rev() {
    println!("{number}!");
}
Enter fullscreen mode Exit fullscreen mode

🔐 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
Enter fullscreen mode Exit fullscreen mode

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 { ... }
Enter fullscreen mode Exit fullscreen mode

🔄 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() }
Enter fullscreen mode Exit fullscreen mode

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");
}
Enter fullscreen mode Exit fullscreen mode

⚠️ 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"
Enter fullscreen mode Exit fullscreen mode

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,
}
Enter fullscreen mode Exit fullscreen mode

Instansiasi Struct

  • Gunakan {} dengan pasangan field: value.
let user1 = User {
    email: String::from("someone@example.com"),
    username: String::from("someuser"),
    active: true,
    sign_in_count: 1,
};
Enter fullscreen mode Exit fullscreen mode

Struct Update Syntax

  • Gunakan .. untuk menyalin nilai dari struct lain.
let user2 = User {
    email: String::from("another@example.com"),
    ..user1
};
Enter fullscreen mode Exit fullscreen mode

⚠️ 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);
Enter fullscreen mode Exit fullscreen mode

Unit-like Struct

  • Struct tanpa field, berguna untuk trait atau marker.
struct AlwaysEqual;
Enter fullscreen mode Exit fullscreen mode

📐 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
    }
}
Enter fullscreen mode Exit fullscreen mode

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,
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

📏 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,
}
Enter fullscreen mode Exit fullscreen mode

💡 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,
}
Enter fullscreen mode Exit fullscreen mode

Enum dengan Data

  • Variant bisa membawa data, mirip struct atau tuple.
enum IpAddr {
    V4(String),
    V6(String),
}
Enter fullscreen mode Exit fullscreen mode

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,
}
Enter fullscreen mode Exit fullscreen mode
  • Enum lebih ringkas:
enum IpAddr {
    V4(String),
    V6(String),
}
Enter fullscreen mode Exit fullscreen mode

✨ 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,
}
Enter fullscreen mode Exit fullscreen mode

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}"),
    _ => (),
}
Enter fullscreen mode Exit fullscreen mode

📦 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;
Enter fullscreen mode Exit fullscreen mode

Pattern Matching dengan Option

match x {
    Some(i) => Some(i + 1),
    None => None,
}
Enter fullscreen mode Exit fullscreen mode
  • Rust memaksa penanganan semua kemungkinan (Some dan None) untuk mencegah bug.

🧠 Idiomatik: if let

  • Sintaks ringkas untuk menangani satu pola.
if let Some(3) = some_value {
    println!("Three!");
}
Enter fullscreen mode Exit fullscreen mode

💡 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.

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 atau lib.rs).
  • File atau folder yang sesuai harus tersedia:
    • mod garden; → akan cari di garden.rs atau garden/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() {}
Enter fullscreen mode Exit fullscreen mode
  • Gunakan use untuk mengimpor path agar lebih singkat.
use crate::front_of_house::hosting;
hosting::add_to_waitlist();
Enter fullscreen mode Exit fullscreen mode

📚 Paths dalam Module

Absolute Path

  • Dimulai dari crate root.
crate::front_of_house::hosting::add_to_waitlist();
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

💡 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
Enter fullscreen mode Exit fullscreen mode

Akses dan Iterasi

  • Gunakan indeks: v[0], atau .get(index) untuk hasil Option.
  • Iterasi menggunakan for:
for i in &v { println!("{i}"); }
Enter fullscreen mode Exit fullscreen mode

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!");
Enter fullscreen mode Exit fullscreen mode

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}"); }
Enter fullscreen mode Exit fullscreen mode

🗺️ 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);
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

💡 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:

  1. Unrecoverable Errors: Tidak bisa diperbaiki, program akan dihentikan.
  2. 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!");
Enter fullscreen mode Exit fullscreen mode

Default Behavior

  • Rust melakukan unwinding: membersihkan stack.
  • Bisa diubah menjadi abort untuk binary lebih kecil:
[profile.release]
panic = 'abort'
Enter fullscreen mode Exit fullscreen mode

✅ Recoverable Errors dengan Result<T, E>

Struktur

enum Result<T, E> {
    Ok(T),
    Err(E),
}
Enter fullscreen mode Exit fullscreen mode
  • 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) => ..., 
}
Enter fullscreen mode Exit fullscreen mode

Pattern Matching

Gunakan match atau if let untuk menangani Result.

if let Ok(file) = File::open("hello.txt") {
    // gunakan file
}
Enter fullscreen mode Exit fullscreen mode

❓ Operator ? untuk Propagasi Error

Fungsi

  • Mempermudah propagasi error ke pemanggil fungsi.
  • Jika hasil Result adalah Err, maka akan langsung return dari fungsi.
fn read_file() -> Result<String, std::io::Error> {
    let mut f = File::open("hello.txt")?;
    // ...
    Ok(contents)
}
Enter fullscreen mode Exit fullscreen mode

🔧 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 dalam Result.

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 {
    // ...
}
Enter fullscreen mode Exit fullscreen mode

Penggunaan Generic

  • Fungsi: Gunakan T sebagai pengganti tipe konkret.
  • Struct:
  struct Point<T> {
      x: T,
      y: T,
  }
Enter fullscreen mode Exit fullscreen mode
  • Enum:
  enum Option<T> {
      Some(T),
      None,
  }
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

Trait Bound

  • Digunakan untuk membatasi generic hanya ke tipe yang mengimplementasikan trait tertentu.
fn notify<T: Summary>(item: T) {
    println!("Breaking news! {}", item.summarize());
}
Enter fullscreen mode Exit fullscreen mode
  • 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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

💡 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
Enter fullscreen mode Exit fullscreen mode
#[test]
fn it_works() {
    let result = add(2, 2);
    assert_eq!(result, 4);
}
Enter fullscreen mode Exit fullscreen mode

Tiga Langkah Umum Test

  1. Setup data atau state.
  2. Jalankan kode yang ingin diuji.
  3. 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);
    }
}
Enter fullscreen mode Exit fullscreen mode

🔗 Integration Test

Karakteristik:

  • Diletakkan di folder tests/ di luar src/.
  • 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);
}
Enter fullscreen mode Exit fullscreen mode

⚙️ Pengaturan Tambahan

  • #[should_panic]: Menandai bahwa test seharusnya gagal.
  • #[ignore]: Melewatkan test secara default, bisa dijalankan dengan cargo test -- --ignored.
#[test]
#[should_panic(expected = "panic message")]
fn it_panics() {
    panic!("panic message");
}
Enter fullscreen mode Exit fullscreen mode

📚 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 %}
Enter fullscreen mode Exit fullscreen mode



---

## 🧠 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);
}
Enter fullscreen mode Exit fullscreen mode

2. Struktur Config

  • Memisahkan parsing ke dalam struct Config.
  • Fungsi Config::build mengembalikan Result<Config, &str>.
struct Config {
    query: String,
    file_path: String,
}
Enter fullscreen mode Exit fullscreen mode

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(())
}
Enter fullscreen mode Exit fullscreen mode

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()
}
Enter fullscreen mode Exit fullscreen mode

🧪 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));
}
Enter fullscreen mode Exit fullscreen mode

🌐 Environment Variable untuk Case-Insensitive

  • Jika CASE_INSENSITIVE diset, program akan menggunakan pencarian yang tidak sensitif terhadap kapital.
env::var("CASE_INSENSITIVE").is_ok()
Enter fullscreen mode Exit fullscreen mode

🖨️ 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;
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

🔁 Iterators

Definisi

  • Iterator adalah trait dengan satu method wajib: next(), yang mengembalikan Option<T>.
trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
}
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

🧪 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();
Enter fullscreen mode Exit fullscreen mode

⚡ 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"
Enter fullscreen mode Exit fullscreen mode

Akun crates.io

  1. Login ke crates.io dengan akun GitHub.
  2. Ambil token API dari crates.io/me.
  3. Jalankan:
   cargo login <token>
Enter fullscreen mode Exit fullscreen mode

🚀 Publikasi ke crates.io

Langkah-langkah

  1. Cek crate unik (nama belum digunakan).
  2. Tambahkan metadata.
  3. Jalankan:
   cargo publish
Enter fullscreen mode Exit fullscreen mode

⚠️ 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
Enter fullscreen mode Exit fullscreen mode
  • 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 %}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

💡 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);
Enter fullscreen mode Exit fullscreen mode

👥 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);
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

🎭 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!");
    }
}
Enter fullscreen mode Exit fullscreen mode

🔁 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;
Enter fullscreen mode Exit fullscreen mode

💡 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 dan Drop 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!");
});
Enter fullscreen mode Exit fullscreen mode

Join

  • Untuk menunggu thread selesai, gunakan .join().
let handle = thread::spawn(|| { ... });
handle.join().unwrap();
Enter fullscreen mode Exit fullscreen mode

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);
});
Enter fullscreen mode Exit fullscreen mode

📡 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();
Enter fullscreen mode Exit fullscreen mode

Cloning Sender

  • Bisa membuat banyak pengirim (tx) dengan tx.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;
}
Enter fullscreen mode Exit fullscreen mode

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));
Enter fullscreen mode Exit fullscreen mode

💡 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> dan Arc<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

  1. Objects: Kombinasi data dan fungsi.
  2. Encapsulation: Menyembunyikan detail implementasi.
  3. Inheritance: Pewarisan antar tipe.
  4. 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>>,
}
Enter fullscreen mode Exit fullscreen mode

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) { ... }
}
Enter fullscreen mode Exit fullscreen mode

🔄 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 dalam Box<dyn Draw>.
  • Disimpan dalam koleksi dan semuanya bisa dipanggil .draw().
for component in screen.components.iter() {
    component.draw();
}
Enter fullscreen mode Exit fullscreen mode

💡 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:
    1. Menerima koneksi TCP.
    2. Membaca dan mem-parsing request HTTP.
    3. Mengirimkan response sederhana.
    4. 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();
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

🧵 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 menjalankan Job dari channel.

🧹 Graceful Shutdown

  • Implementasi trait Drop untuk ThreadPool.
  • 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 ke HashMap
  • 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)