1. Pengenalan Rust

Rust adalah bahasa pemrograman sistem yang pertama kali dirilis oleh Mozilla pada tahun 2010. Rust dirancang untuk menjadi "safe, concurrent, practical" dan telah dinobatkan sebagai "most loved programming language" dalam Stack Overflow Survey selama 8 tahun berturut-turut.

Karakteristik Utama Rust:

  • Memory Safety tanpa GC - Menggunakan sistem ownership dan borrowing
  • Zero-cost abstractions - Abstraksi tingkat tinggi tanpa overhead
  • Thread tanpa data race - Konkurensi yang aman
  • Pattern matching - Matching yang ekspresif
  • Cargo - Build system dan package manager yang powerful
Fun fact: Rust adalah bahasa pertama selain C yang digunakan untuk mengembangkan kernel Linux (2022).
Contoh program pertama: hello.rs
fn main() {
    // Menampilkan teks ke console
    println!("Hello, World!");
    println!("Selamat belajar Rust!");
    
    // Variabel sederhana
    let nama = "Rust";
    let tahun = 2010;
    println!("{} dirilis tahun {}", nama, tahun);
}
Tips: Fungsi println! dengan tanda seru adalah macro, bukan function. Macro di Rust memungkinkan metaprogramming dan lebih fleksibel daripada function biasa.

2. Instalasi & Cargo

Instalasi Rust (rustup)

Cara termudah menginstall Rust adalah menggunakan rustup - toolchain installer resmi.

Install rustup (Linux/macOS)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Windows
Download dan jalankan rustup-init.exe dari https://rustup.rs/

Cargo - Package Manager Rust

Cargo adalah tool yang mengelola:

  • Membuat project baru (cargo new)
  • Menjalankan project (cargo run)
  • Mengecek kode (cargo check)
  • Menambahkan dependencies (cargo add)
  • Build project (cargo build)
Membuat project baru dengan Cargo
# Membuat project baru
cargo new my_project
cd my_project

# Struktur folder:
# my_project/
#   ├── Cargo.toml  (konfigurasi dan dependencies)
#   └── src/
#       └── main.rs (kode utama)

# Menjalankan project
cargo run

# Build untuk production
cargo build --release

# Mengecek kode tanpa kompilasi
cargo check

3. Sintaks Dasar

Aturan dasar penulisan Rust:

  • File Rust berekstensi .rs
  • Entry point adalah fungsi fn main()
  • Statement diakhiri dengan titik koma (;) (kecuali expression)
  • Blok kode menggunakan kurung kurawal { }
  • Komentar: // untuk single line, /* */ untuk multi-line
  • Nama variabel menggunakan snake_case
  • Nama fungsi juga menggunakan snake_case
Contoh sintaks dasar
// Ini komentar single line

/*
    Ini komentar multi-line
    Bisa beberapa baris
*/

fn main() {
    // Statement diakhiri ;
    println!("Hello, Rust!");
    
    // Expression (tidak pakai ;)
    let y = {
        let x = 3;
        x + 1   // Nilai dari block ini
    };
    
    println!("Nilai y: {}", y);
}
Penting: Di Rust, statement melakukan sesuatu dan tidak mengembalikan nilai, sedangkan expression menghasilkan nilai. Expression tidak diakhiri titik koma.

4. Variabel & Mutabilitas

Konsep unik Rust: Immutable by default

Di Rust, variabel immutable (tidak bisa diubah) secara default. Ini adalah fitur keamanan yang mendorong coding defensif.

Cara deklarasi:

let x = 5; → immutable
let mut y = 5; → mutable (bisa diubah)
const MAX: u32 = 100; → konstanta (harus dengan tipe)

Shadowing

Rust mengizinkan deklarasi ulang variabel dengan nama yang sama (shadowing).

Contoh variabel & mutabilitas
fn main() {
    // Immutable (default)
    let x = 5;
    println!("x = {}", x);
    // x = 6; // ERROR! tidak bisa mengubah immutable variable
    
    // Mutable
    let mut y = 5;
    println!("y awal = {}", y);
    y = 10; // Bisa diubah karena pakai mut
    println!("y setelah diubah = {}", y);
    
    // Konstanta
    const MAX_POINTS: u32 = 100_000;
    println!("MAX_POINTS = {}", MAX_POINTS);
    
    // Shadowing
    let angka = 5;
    let angka = angka + 10; // shadowing, variabel baru
    let angka = angka * 2;
    println!("angka setelah shadowing = {}", angka); // 30
    
    // Shadowing dengan tipe berbeda
    let teks = "hello";
    let teks = teks.len(); // sekarang jadi angka (usize)
    println!("panjang teks = {}", teks);
}
Tips: Gunakan let mut hanya jika variabel benar-benar perlu diubah. Immutable default membantu mencegah bug yang tidak terduga.

5. Tipe Data

Tipe data skalar (Scalar Types):

Kategori Tipe Deskripsi
Integeri8, i16, i32, i64, i128Signed integer (bisa negatif)
u8, u16, u32, u64, u128Unsigned integer (hanya positif)
isize, usizeUkuran tergantung arsitektur (32/64 bit)
Floatingf32, f64Bilangan desimal
Booleanbooltrue atau false
CharactercharUnicode character (4 bytes)

Tipe data compound:

  • Tuple - Kumpulan nilai dengan tipe bisa berbeda
  • Array - Kumpulan nilai dengan tipe sama, panjang tetap
  • Vector (dari std) - Array dinamis
Contoh tipe data
fn main() {
    // Integer (default i32)
    let a = 98_222; // bisa pakai underscore untuk readability
    let b = 0xff;   // hex
    let c = 0o77;   // octal
    let d = 0b1111_0000; // binary
    
    // Floating (default f64)
    let x = 2.0;
    let y: f32 = 3.0;
    
    // Boolean
    let t = true;
    let f: bool = false;
    
    // Character (Unicode)
    let c = 'z';
    let z = 'ℤ';
    let heart = '❤';
    let emoji = '😊';
    
    // Tuple
    let tup: (i32, f64, char) = (500, 6.4, 'a');
    let (x, y, z) = tup; // destructuring
    println!("y = {}", y);
    println!("tup.0 = {}", tup.0); // akses index
    
    // Array (fixed size)
    let arr = [1, 2, 3, 4, 5];
    let first = arr[0];
    let months = ["Jan", "Feb", "Mar", "Apr"];
    
    // Array dengan tipe eksplisit
    let arr2: [i32; 5] = [1, 2, 3, 4, 5];
    let arr3 = [3; 5]; // [3, 3, 3, 3, 3]
}

6. Functions

Struktur function:

fn nama_function(param1: Tipe1, param2: Tipe2) -> ReturnType {
    // body
    return nilai; // atau nilai tanpa return untuk expression
}

Poin penting:

  • Parameter harus dideklarasikan tipenya
  • Return type opsional (jika tidak ada, return unit ())
  • Expression terakhir dalam function bisa menjadi return value (tanpa return dan tanpa ;)
Contoh functions
fn main() {
    println!("5 + 3 = {}", tambah(5, 3));
    println!("5 * 3 = {}", kali(5, 3));
    
    let hasil = kuadrat(7);
    println!("7 kuadrat = {}", hasil);
    
    sapa("Andi");
}

// Function dengan return (cara 1: explicit return)
fn tambah(a: i32, b: i32) -> i32 {
    return a + b;
}

// Function dengan return (cara 2: implicit return - expression)
fn kali(a: i32, b: i32) -> i32 {
    a * b  // tanpa titik koma, ini expression
}

// Function dengan satu parameter
fn kuadrat(x: i32) -> i32 {
    x * x
}

// Function tanpa return (void) -> return unit ()
fn sapa(nama: &str) {
    println!("Halo, {}!", nama);
    // secara implisit return ()
}

// Function dengan multiple statements
fn hitung(a: i32, b: i32) -> i32 {
    let hasil = a + b;
    let hasil2 = hasil * 2;
    hasil2 // implicit return
}

7. Control Flow

if-else di Rust

if di Rust adalah expression, bukan statement. Artinya bisa mengembalikan nilai.

Loop:

  • loop - infinite loop, bisa dihentikan dengan break
  • while - loop dengan kondisi
  • for - loop untuk iterasi range atau collection
Contoh control flow
fn main() {
    // if sebagai expression
    let nilai = 75;
    let grade = if nilai >= 90 {
        'A'
    } else if nilai >= 80 {
        'B'
    } else if nilai >= 70 {
        'C'
    } else {
        'D'
    };
    println!("Grade: {}", grade);
    
    // loop dengan break return value
    let mut counter = 0;
    let result = loop {
        counter += 1;
        if counter == 10 {
            break counter * 2; // loop mengembalikan nilai 20
        }
    };
    println!("Result: {}", result);
    
    // while loop
    let mut number = 3;
    while number != 0 {
        println!("{}!", number);
        number -= 1;
    }
    println!("GO!");
    
    // for loop dengan range
    println!("For loop 1..5:");
    for i in 1..5 { // 1 sampai 4 (exclusive)
        println!("i = {}", i);
    }
    
    // for loop dengan range inclusive
    println!("For loop 1..=5:");
    for i in 1..=5 { // 1 sampai 5 (inclusive)
        println!("i = {}", i);
    }
    
    // for loop dengan array
    let arr = [10, 20, 30, 40];
    for element in arr.iter() {
        println!("value: {}", element);
    }
    
    // for dengan enumerate
    for (index, value) in arr.iter().enumerate() {
        println!("arr[{}] = {}", index, value);
    }
}
Tips: Range 1..5 bersifat exclusive (1-4), sedangkan 1..=5 inclusive (1-5).

8. Ownership & Borrowing

Apa itu Ownership?

Ownership adalah sistem unik Rust yang menjamin memory safety tanpa garbage collector. Tiga aturan utama:

  1. 1
    Setiap nilai di Rust memiliki variabel yang disebut owner
  2. 2
    Hanya boleh ada satu owner dalam satu waktu
  3. 3
    Ketika owner keluar dari scope, nilai akan di-drop (memory dibebaskan)

Borrowing:

Meminjam reference ke nilai tanpa mengambil ownership. Bisa dilakukan dengan & (immutable borrow) atau &mut (mutable borrow).

Aturan penting: Tidak boleh memiliki mutable borrow dan immutable borrow secara bersamaan dalam scope yang sama.
Contoh Ownership
fn main() {
    // Ownership dasar
    let s1 = String::from("hello"); // s1 adalah owner
    let s2 = s1; // ownership berpindah ke s2
    
    // println!("{}", s1); // ERROR! s1 sudah tidak valid
    println!("{}", s2); // OK
    
    // Clone (jika ingin copy dalam-dalam)
    let s3 = String::from("world");
    let s4 = s3.clone();
    println!("s3 = {}, s4 = {}", s3, s4); // kedua-duanya valid
    
    // Borrowing
    let s5 = String::from("rust");
    let panjang = hitung_panjang(&s5); // meminjam (&s5)
    println!("'{}' panjangnya {}", s5, panjang); // s5 masih valid
    
    // Mutable borrow
    let mut s6 = String::from("hello");
    ubah_string(&mut s6);
    println!("s6 setelah diubah: {}", s6);
    
    // Aturan borrowing
    let mut s7 = String::from("data");
    let r1 = &s7; // immutable borrow
    let r2 = &s7; // immutable borrow (boleh banyak)
    println!("r1 = {}, r2 = {}", r1, r2);
    // let r3 = &mut s7; // ERROR! tidak boleh mutable borrow karena ada immutable
    
    let r3 = &mut s7; // OK karena r1, r2 sudah tidak dipakai
    println!("r3 = {}", r3);
}

fn hitung_panjang(s: &String) -> usize {
    s.len() // s hanya meminjam, tidak punya ownership
} // s keluar scope, tapi karena hanya meminjam, tidak ada yang di-drop

fn ubah_string(s: &mut String) {
    s.push_str(" world"); // mengubah nilai yang dipinjam secara mutable
}
Visualisasi: Ownership seperti "kepemilikan" - jika Anda meminjamkan buku (borrowing), Anda masih punya bukunya. Jika Anda memberikan buku (move), Anda tidak punya lagi.

9. References & Slices

References

Reference memungkinkan Anda merujuk ke suatu nilai tanpa mengambil ownership. Ditandai dengan &.

Slices

Slice adalah reference ke sebagian dari collection (array, String, dll). Slice tidak memiliki ownership.

  • &str - string slice
  • &[i32] - array slice
Contoh References & Slices
fn main() {
    // String slice (&str)
    let s = String::from("hello world");
    let hello = &s[0..5]; // slice dari index 0-4
    let world = &s[6..11]; // slice dari index 6-10
    println!("{} {}", hello, world);
    
    // Slice dengan range shorthand
    let slice1 = &s[0..2];   // "he"
    let slice2 = &s[..2];    // sama: "he" (dari awal sampai 2)
    let slice3 = &s[6..];     // dari 6 sampai akhir: "world"
    let slice4 = &s[..];      // seluruh string
    
    // Array slice
    let arr = [1, 2, 3, 4, 5];
    let slice_arr = &arr[1..4]; // [2, 3, 4]
    println!("Array slice: {:?}", slice_arr);
    
    // Function dengan string slice
    let kata = String::from("rust programming");
    let first = first_word(&kata);
    println!("Kata pertama: {}", first);
    
    // Literal string (&str)
    let literal: &str = "ini literal string";
    let first_literal = first_word(literal);
    println!("First word dari literal: {}", first_literal);
}

// Function yang menerima string slice
fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();
    
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }
    
    &s[..] // jika tidak ada spasi, return seluruh string
}

10. Structs

Struct adalah cara membuat tipe data kustom

Tiga jenis struct di Rust:

  • Classic struct - dengan field bernama
  • Tuple struct - tanpa nama field, hanya tipe
  • Unit struct - tidak punya field

Method dan Associated Functions:

Method didefinisikan dalam blok impl.

Contoh Structs
// Definisi struct classic
struct User {
    username: String,
    email: String,
    active: bool,
    sign_in_count: u64,
}

// Tuple struct
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

// Unit struct
struct UnitStruct;

// Implementation block (methods)
impl User {
    // Associated function (constructor pattern)
    fn new(username: String, email: String) -> User {
        User {
            username,
            email,
            active: true,
            sign_in_count: 1,
        }
    }
    
    // Method (self sebagai parameter)
    fn activate(&mut self) {
        self.active = true;
    }
    
    fn deactivate(&mut self) {
        self.active = false;
    }
    
    // Getter (self reference)
    fn is_active(&self) -> bool {
        self.active
    }
    
    // Method yang mengubah self
    fn increment_sign_in(&mut self) {
        self.sign_in_count += 1;
    }
}

fn main() {
    // Membuat instance struct
    let mut user1 = User {
        username: String::from("alzzdev"),
        email: String::from("alzz@example.com"),
        active: true,
        sign_in_count: 1,
    };
    
    // Mengakses field
    println!("Username: {}", user1.username);
    user1.username = String::from("alzzdevmaret");
    
    // Struct update syntax (..)
    let user2 = User {
        username: String::from("user2"),
        email: String::from("user2@example.com"),
        ..user1 // sisanya dari user1 (active, sign_in_count)
    };
    // Catatan: user1 tidak bisa dipakai lagi karena String di-move
    
    // Menggunakan associated function
    let mut user3 = User::new(
        String::from("rust_user"),
        String::from("rust@example.com")
    );
    
    // Menggunakan methods
    println!("User3 active: {}", user3.is_active());
    user3.deactivate();
    println!("User3 setelah deactivate: {}", user3.is_active());
    user3.activate();
    user3.increment_sign_in();
    
    // Tuple struct
    let black = Color(0, 0, 0);
    let origin = Point(0, 0, 0);
    println!("Black: {}, {}, {}", black.0, black.1, black.2);
    
    // Destructuring tuple struct
    let Color(r, g, b) = black;
    println!("r={}, g={}, b={}", r, g, b);
}

11. Enums & Pattern Matching

Enum di Rust sangat powerful

Enum bisa menyimpan data yang berbeda-beda untuk setiap variant.

Option Enum:

Rust tidak punya null. Sebagai gantinya, menggunakan Option<T>:

enum Option<T> {
    Some(T),
    None,
}

Result Enum:

Untuk error handling:

enum Result<T, E> {
    Ok(T),
    Err(E),
}

Pattern Matching dengan match:

match adalah kontrol flow yang sangat ekspresif.

Contoh Enums & Pattern Matching
// Definisi enum
enum Message {
    Quit,                       // tanpa data
    Move { x: i32, y: i32 },    // dengan struct anonymous
    Write(String),              // dengan satu nilai
    ChangeColor(i32, i32, i32), // tuple struct
}

// Enum bisa punya methods juga
impl Message {
    fn call(&self) {
        match self {
            Message::Quit => println!("Quit"),
            Message::Move { x, y } => println!("Move to ({}, {})", x, y),
            Message::Write(text) => println!("Write: {}", text),
            Message::ChangeColor(r, g, b) => println!("Change color: {}, {}, {}", r, g, b),
        }
    }
}

fn main() {
    // Menggunakan enum
    let m1 = Message::Write(String::from("hello"));
    let m2 = Message::Move { x: 10, y: 20 };
    let m3 = Message::ChangeColor(255, 0, 0);
    
    m1.call();
    m2.call();
    m3.call();
    
    // Option enum
    let some_number = Some(5);
    let some_string = Some("a string");
    let absent_number: Option = None;
    
    // Pattern matching dengan Option
    fn plus_one(x: Option) -> Option {
        match x {
            None => None,
            Some(i) => Some(i + 1),
        }
    }
    
    let five = Some(5);
    let six = plus_one(five);
    let none = plus_one(None);
    
    // match dengan exhaustive checking
    fn describe_number(x: i32) -> &'static str {
        match x {
            0 => "zero",
            1 => "one",
            2..=9 => "between 2 and 9",
            _ if x < 0 => "negative number",
            _ => "something else",
        }
    }
    
    println!("{}", describe_number(5));
    println!("{}", describe_number(-3));
    
    // if let (syntax sugar untuk match satu case)
    let some_value = Some(3);
    if let Some(3) = some_value {
        println!("nilainya tiga!");
    }
    
    // while let
    let mut stack = Vec::new();
    stack.push(1);
    stack.push(2);
    stack.push(3);
    
    while let Some(top) = stack.pop() {
        println!("{}", top);
    }
}

12. Error Handling

Dua tipe error di Rust:

1. Recoverable errors dengan Result<T, E>

Untuk error yang bisa diatasi (file not found, dll).

2. Unrecoverable errors dengan panic!

Untuk error yang tidak bisa diatasi (bug, array out of bounds).

Propagating errors dengan ? operator

Operator ? mempermudah propagasi error.

Contoh Error Handling
use std::fs::File;
use std::io::{self, Read};

fn main() {
    // panic! (unrecoverable)
    // panic!("crash and burn");
    
    // panic karena out of bounds
    // let v = vec![1, 2, 3];
    // v[99]; // akan panic
    
    // Result enum
    let f = File::open("hello.txt");
    
    let f = match f {
        Ok(file) => file,
        Err(error) => panic!("Problem opening file: {:?}", error),
    };
    
    // Alternatif dengan expect
    let f = File::open("hello.txt").expect("Failed to open hello.txt");
    
    // Propagating errors
    fn read_username_from_file() -> Result {
        let f = File::open("hello.txt");
        
        let mut f = match f {
            Ok(file) => file,
            Err(e) => return Err(e),
        };
        
        let mut s = String::new();
        
        match f.read_to_string(&mut s) {
            Ok(_) => Ok(s),
            Err(e) => Err(e),
        }
    }
    
    // Propagating dengan ? operator
    fn read_username_from_file_short() -> Result {
        let mut f = File::open("hello.txt")?;
        let mut s = String::new();
        f.read_to_string(&mut s)?;
        Ok(s)
    }
    
    // Lebih pendek lagi
    fn read_username_from_file_shortest() -> Result {
        let mut s = String::new();
        File::open("hello.txt")?.read_to_string(&mut s)?;
        Ok(s)
    }
    
    // Kombinasi dengan match dan Option
    fn find_first_number(text: &str) -> Option {
        for word in text.split_whitespace() {
            if let Ok(num) = word.parse::() {
                return Some(num);
            }
        }
        None
    }
    
    // Custom error handling
    #[derive(Debug)]
    enum MyError {
        IoError(io::Error),
        ParseError(std::num::ParseIntError),
    }
    
    impl From for MyError {
        fn from(err: io::Error) -> MyError {
            MyError::IoError(err)
        }
    }
    
    impl From for MyError {
        fn from(err: std::num::ParseIntError) -> MyError {
            MyError::ParseError(err)
        }
    }
    
    fn complex_function() -> Result {
        let mut s = String::new();
        File::open("data.txt")?.read_to_string(&mut s)?;
        let num = s.trim().parse::()?;
        Ok(num)
    }
}
Best practice: Gunakan panic! hanya untuk contoh/prototype atau ketika program mencapai state yang memang tidak mungkin pulih. Untuk error yang bisa diantisipasi, gunakan Result.