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
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);
}
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.
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
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
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
// 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);
}
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).
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);
}
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 |
|---|---|---|
| Integer | i8, i16, i32, i64, i128 | Signed integer (bisa negatif) |
u8, u16, u32, u64, u128 | Unsigned integer (hanya positif) | |
isize, usize | Ukuran tergantung arsitektur (32/64 bit) | |
| Floating | f32, f64 | Bilangan desimal |
| Boolean | bool | true atau false |
| Character | char | Unicode 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
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
returndan tanpa;)
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 denganbreakwhile- loop dengan kondisifor- loop untuk iterasi range atau collection
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);
}
}
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
Setiap nilai di Rust memiliki variabel yang disebut owner
-
2
Hanya boleh ada satu owner dalam satu waktu
-
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).
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
}
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
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.
// 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.
// 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.
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)
}
}
panic! hanya untuk contoh/prototype atau ketika program
mencapai state yang memang tidak mungkin pulih. Untuk error yang bisa diantisipasi, gunakan Result.