├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── lain-lain.md ├── pull_request_template.md └── workflows │ └── build.yml ├── .gitignore ├── .mergify ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE ├── README.md ├── algorithms ├── Cargo.toml └── src │ ├── lib.rs │ └── sorting │ ├── bead_sort.rs │ ├── bogo_sort.rs │ └── mod.rs ├── basics ├── 01_introduction │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── lib.rs ├── 02_variables │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── lib.rs ├── 03_functions │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── lib.rs ├── 04_ownership │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── lib.rs ├── 05_enumeration_dan_pattern_matching │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── example.rs │ │ └── lib.rs ├── 06_result_dan_option_type │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── example.rs │ │ └── lib.rs ├── 07_conditional │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── lib.rs ├── 08_loop │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── lib.rs ├── 09_struct_dan_implementation │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── lib.rs └── 10_trait │ ├── Cargo.toml │ ├── README.md │ └── src │ └── lib.rs ├── extras └── smart_pointers │ ├── 01_box.md │ ├── 02_deref_trait.md │ ├── 03_drop_trait.md │ ├── Cargo.toml │ └── src │ └── lib.rs ├── git_hooks └── pre-commit └── intermediate ├── 01_generics ├── Cargo.toml ├── README.md └── src │ └── lib.rs └── 02_lifetime ├── Cargo.toml ├── README.md └── src └── lib.rs /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: buat report untuk membantu kita dalam menyelesaikan masalah pada repository 4 | title: BUG 5 | labels: bug 6 | assignees: MMADUs 7 | 8 | --- 9 | 10 | ## Masalah 11 | 12 | 13 | ## Screenshots atau contoh error 14 | 15 | ``` 16 | pesan error 17 | ``` 18 | 19 | ## Desktop (please complete the following information): 20 | 21 | - OS: ``linux/windows/macOS`` 22 | - Rust: ``version`` 23 | 24 | 25 | terima kasih ! -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/lain-lain.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Lain Lain 3 | about: buat report deskripsi untuk pull request 4 | title: Penambahan algoritma 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Description 11 | 12 | 13 | ## Saya Menggunakan 14 | 15 | OS : ``Linux / Windows / MacOS`` 16 | Rust: ``version`` 17 | 18 | ## tambahan lainnya 19 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Deskripsi (Description) 2 | 3 | 4 | ## Checklist: 5 | ### Umum: 6 | 7 | - [ ] Saya menambah algoritma terbaru. 8 | - [ ] Saya memperbaiki algoritma yang sudah ada. 9 | - [ ] Saya memperbaiki dokumentasi. 10 | - [ ] Saya menambah dokumentasi. 11 | 12 | ### Contributor Requirements (Syarat Kontributor) dan Lain-Lain: 13 | 14 | - [ ] Saya sudah membaca (I have read) [CONTRIBUTING](https://github.com/bellshade/Rust/blob/main/CONTRIBUTING.md) dan sudah menyetujui semua syarat. 15 | - [ ] Saya menggunakan bahasa Indonesia untuk memberikan penjelasan dari kode yang saya buat. 16 | 17 | ### Unit Testing dan Linting: 18 | 19 | - [ ] cargo fmt 20 | 21 | ## Environment 22 | 23 | Saya menggunakan (I'm using): 24 | 25 | - ``os`` = ``linux / windows / macOS`` 26 | - ``rust`` = ``rustc --version`` 27 | 28 | 29 | 30 | linked issue #NOMOR_ISSUE 31 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: 3 | push: 4 | branches: [main] 5 | pull_request: 6 | branches: [main] 7 | 8 | 9 | jobs: 10 | fmt: 11 | # cargo fmt 12 | name: cargo fmt 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: cargo fmt 17 | run: cargo fmt 18 | 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | *target* 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | # Jetbrains ignore 13 | .idea -------------------------------------------------------------------------------- /.mergify: -------------------------------------------------------------------------------- 1 | pull_request_rules: 2 | # membuat requirement dari automerge dengan sistem 3 | # ketika workflow build passed maka akan dicentang 4 | # ketika target pull request ke main maka akan dicentang 5 | # ketika dikasih label 'ready-to-merge' akan dicentang 6 | 7 | # ketika requirement telah terpenuhi maka bot akan bekerja 8 | # dan pull request secara otomatis akan merge 9 | - name: ci testing kalo sukses ke squash dengan label 10 | conditions: 11 | - base=main 12 | - label=ready-to-merge 13 | actions: 14 | # menambahkan komentar jika sudah di merge 15 | comment: 16 | message: terima kasih atas kontribusinya @{{author}}! 17 | # menambahkan label jika sudah di merge 18 | label: 19 | add: 20 | - sudah direview! 21 | # metode sistem dari automerge 22 | merge: 23 | method: squash 24 | 25 | # membuat label dengan kondisi jika di dalam pull request 26 | # terdapat file java 27 | - name: Rust label 28 | conditions: 29 | - files~=\.rs$ 30 | actions: 31 | # menabahkan label 'java files' 32 | # dan lebel 'request tim java untuk review' 33 | label: 34 | add: 35 | - Rust file 36 | - request tim Rust untuk review 37 | 38 | # membuat label dengan kondisi jika di dalam pull request 39 | # terdapat file markdown 40 | - name: markdown label 41 | conditions: 42 | - files~=\.md$ 43 | actions: 44 | # menambahkan label 'markdown files' 45 | label: 46 | add: 47 | - markdown files 48 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Panduan Berkontribusi 2 | 3 | ## Kontributor 4 | 5 | Kami sangat senang dan berterima kasih bila anda ikut berkontribusi dalam repositori ini. 6 | Semua boleh ikut berkontribusi walaupun hal kecil dengan pengecualian sebagai berikut: 7 | 8 | - Hasil pekerjaan kamu adalah buatan kamu sendiri dan tidak ada hak cipta dari orang lain. 9 | - Jika ditemukan kesamaan, maka tidak akan kami `merge`. 10 | - Hasil kerja kamu akan berlisensi [MIT](LICENSE) ketika permintaan pull kamu sudah di merged. 11 | - Hasil kerja kamu wajib mengikuti standar dan style koding dari kami. 12 | - Penggunaan nama file bersifat `snake_case` dan berlaku juga untuk variable dan identifier. 13 | - Menggunakan output `println!`. 14 | - Menghindari penggunaan library pada koding (jika dibutuhkan silahkan diskusi di [issue](https://github.com/bellshade/Rust/issues)). 15 | 16 | ## Apa Itu Algoritma? 17 | 18 | Algoritma adalah langkah-langkah untuk menyelesaikan suatu pekerjaan di mana terdiri dari 3 bagian utama, yaitu: 19 | 20 | - Input/masukan, sebelum menjalankan sebuah algoritma maka hal yang pertama harus dilakukan adalah menerima masukan, input dapat berasal dari pengguna ataupun dari langkah sebelumnya. 21 | - Proses, bagian utama dari algoritma yang melakukan pengolahan input yang akan menghasilkan output. 22 | - Output/keluaran, output adalah hasil dari bagian proses, output ini juga bisa digunakan untuk langkah selanjutnya (jika masih ada). 23 | 24 | Algoritma harus dikemas sedemikian rupa sehingga memudahkan pembaca untuk memasukkannya ke dalam program yang lebih besar. 25 | 26 | Algoritma harus: 27 | 28 | - Memiliki nama kelas dan fungsi intuitif yang memperjelas tujuannya bagi pembaca 29 | - Menggunakan konvensi penamaan Javascript dan nama variabel intuitif untuk memudahkan pemahaman 30 | - Fleksibel untuk mengambil nilai input yang berbeda 31 | - Memiliki docstrings dengan penjelasan yang jelas dan/atau URL ke materi sumber 32 | - Berisi doctests yang menguji nilai input yang valid dan salah 33 | - Kembalikan semua hasil perhitungan alih-alih mencetak atau memplotnya 34 | 35 | ``mod.rs`` harus berisi seperti : 36 | ```Rust 37 | mod algoritma_saya; 38 | 39 | pub use self::algoritma_saya::algoritma_saya; 40 | ``` 41 | 42 | ``algoritma_saya.rs`` berisi testing hasil fungsi algoritma yang kamu buat sebagai contoh : 43 | ```Rust 44 | pub fn algoritma_saya() { 45 | // contoh algoritma 46 | } 47 | 48 | #[cfg(test)] 49 | mod test { 50 | #[test] 51 | fn testing_saya() { 52 | // isi dari testing 53 | } 54 | } 55 | ``` 56 | 57 | Running dengan cara 58 | - ``cargo test`` 59 | - ``cargo fmt`` 60 | - ``cargo clippy -all -- -D warnings 61 | 62 | ## Pull Request 63 | 64 | ### Pull Request Yang Baik 65 | - Lakukan fork pada repositori kami 66 | - setelah melakukan fork, kamu dibebaskan untuk mengubah atau menambah algoritma 67 | - Untuk pull reqquest merubah diusahakan kamu menerapkan algoritma yang lebih baik dan lebih mudah 68 | - Setelah merubah, menambah, atau perbaikan dokumentasi, usahakn kamu membuat branch baru 69 | 70 | ```bash 71 | git checkout -b 72 | git add . 73 | git commit -m "add: menambahkan algoritma terbaru" 74 | ``` 75 | 76 | - Lakukan push ke branch kamu dn kemudian open pull request 77 | 78 | ### Pesan commit 79 | 80 | Pesan / message commit harus mengikuti conventional commit. Kami menggunakan bot label agar tidak susah dalam labeling. 81 | Berikut adalah jenis - jenis pesan commit. 82 | 83 | - `fix` : untuk memperbaiki bug (label `bug`) 84 | - `feat`: untuk menambhkan algoritma terbaru 85 | - `docs`: untuk menambahahkan dokumentasi 86 | 87 | Referensi: 88 | [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) 89 | 90 | ### Contoh penggunaan 91 | 92 | ```bash 93 | git commit -m "docs: menambahkan dokumentasi" 94 | ``` 95 | 96 | ```bash 97 | git commit -m "feat: menambahkan algoritma terbaru" 98 | ``` 99 | 100 | Pull request `merged` jika: 101 | 102 | - Mengikuti standar dan arahan dari `CONTRIBUTING.md` 103 | - Lulus test dan cek dari beberapa test yang sudah kami siapkan 104 | 105 | ## Tambahan 106 | 107 | - Jika ada kendala atau masalah dalam pull request, kamu bisa laporkan masalah pada [issue](https://github.com/bellshade/Rust/issues) 108 | - Jika ada test yang tidak lewat atau gagal, kami akan mengecek kembali perubahan. 109 | 110 | Untuk pull request kami sarankan untuk menjelaskan secara detail yang kamu ubah atau tambahkan, dan bersikap sopan, serta selalu berterima kasih, itu salah satu bentuk tata krama yang baik terhadap sesama contributor dan programmer lainnya.terima kasih sudah berkontribusi di **Rust**. 111 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "basics/01_introduction", 4 | "basics/02_variables", 5 | "basics/03_functions", 6 | "basics/04_ownership", 7 | "basics/05_enumeration_dan_pattern_matching", 8 | "basics/06_result_dan_option_type", 9 | "basics/07_conditional", 10 | "basics/08_loop", 11 | "basics/09_struct_dan_implementation", 12 | "basics/10_trait", 13 | 14 | "intermediate/01_generics", 15 | "intermediate/02_lifetime", 16 | 17 | "algorithms", 18 | 19 | "extras/smart_pointers" 20 | ] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Bellshade 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |

3 | Part of Bellshade Project, managed by WPU Discord Community
4 | WPU Community is the fastest growing software developer forum initiated by Mr. Sandhika Galih
5 | 6 | 7 | 8 |

9 | 10 | --- 11 | 12 | ## Selamat Datang di Repositori Rust Bellshade 13 | 14 | Disini kalian akan belajar tentang Rust, disertai dengan latihan dalam bentuk tests, contoh struktur data dan algoritma yang diimplementasi dalam bahasa Rust, dan hal-hal lainnya. 15 | 16 | ![Rust](https://www.rust-lang.org/static/images/rust-social-wide.jpg) 17 | 18 | Rust merupakan bahasa pemrograman yang awalnya dikembangkan oleh Tony Hoare di Mozilla Foundation. Bahasa ini rilis pada tahun 2014. Kemudian pada 2021 Rust dikembangkan di bawah naungan [Rust Foundation](https://foundation.rust-lang.org/). Rust menjadi bahasa pemrograman paling dicintai berdasarkan survei Stack Overflow selama tujuh tahun berturut-turut (2015 - 2022). 19 | 20 | ### Mengapa Belajar Rust? 21 | 22 | Bahasa Rust memiliki keunikan tersendiri dalam hal keamanan manajemen memori. Rust menekankan programmer untuk mendesain program dengan peraturan tertentu agar programmer tidak melakukan hal-hal yang menimbulkan hal yang tidak aman dalam manajemen memori. Dengan memaksa desain yang aman, bahasa Rust tidak memerlukan bantuan manajemen memori pada _runtime_ program (Seperti _garbage collector_ pada Java) yang dapat menimbulkan _overhead_. Rust sangatlah ekspresif dan juga memiliki fitur-fitur powerful seperti `enum` yang dapat memiliki nilai, `trait` sebagai interface yang dapat diimplementasikan bahkan pada tipe yang tidak kita definisikan, pattern matching, dan `if let`. 23 | 24 | ### Panduan Penggunaan 25 | 26 | Repositori ini memiliki sistem baca-latihan dimana setiap selesai membaca, kalian dapat mengerjakan latihan yang ada dan terkait dengan materi yang telah kalian baca. Kami memanfaatkan fitur `test` pada Rust untuk ini. 27 | 28 | ### Berkontribusi 29 | 30 | Kontribusi kalian akan sangat dihargai dan diperlukan untuk menyempurnakan repositori ini sebagai sarana edukasi gratis. Bila ingin berkontribusi, silahkan fork, dan buka pull request baru pada repositori ini. 31 | -------------------------------------------------------------------------------- /algorithms/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bellshade_algorithms" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | rand = { version = "0.8.5", default-features = false, features = ["small_rng", "std"] } 10 | -------------------------------------------------------------------------------- /algorithms/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod sorting; 2 | -------------------------------------------------------------------------------- /algorithms/src/sorting/bead_sort.rs: -------------------------------------------------------------------------------- 1 | // bead sort atau yang bisa dibilang juga gravity sort 2 | // adalah algoritma yang pada tahun 2002 yang diimplementasikan 3 | // pada perangkat digital yang diyakini dengan pencapaian 4 | // waktu sekitar O(n). namun penerapan algoritma ini lebih lambat 5 | // dalam perangkat luna dan hanya dapat digunakan untuk 6 | // mengurutkan daftar bilangan positif 7 | // dan juga dalam kasusnya yang kebanyakan algoritma ini 8 | // membutuhkan ruang sekitar O(n2). 9 | pub fn bead_sort(a: &mut[usize]) { 10 | // mencari elemen max 11 | let mut maksimal = a[0]; 12 | for i in 1..a.len() { 13 | if a[i] > maksimal { 14 | maksimal = a[i]; 15 | } 16 | } 17 | 18 | // alokasikan memori 19 | let mut beads = vec![vec![0; max]; a.len()]; 20 | 21 | // berikan tanda pada variable beads 22 | for i in 0..al.len() { 23 | for j in (0..a[i]).rev() { 24 | beads[i][j] = 1; 25 | } 26 | } 27 | 28 | // pindah ke bawah variable beads 29 | for j in 0..max { 30 | let mut hasil = 0; 31 | for i in 0..a.len() { 32 | hasil += beads[i][j]; 33 | beads[i][j] = 0; 34 | } 35 | 36 | for k in ((a.len() - sum)..a.len()).rev() { 37 | a[k] = j + 1; 38 | } 39 | } 40 | } 41 | 42 | #[cfg(test)] 43 | // test hasil fungsi yang sudah dibuat 44 | mod tests { 45 | use super::*; 46 | 47 | #[test] 48 | fn menurun() { 49 | let mut var1: [usize; 5] = [5, 4, 3, 2, 1]; 50 | bead_sort(&mut var1); 51 | for i in 0..var1.len() - 1 { 52 | assert!(var1 <= var1[i + 1]); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /algorithms/src/sorting/bogo_sort.rs: -------------------------------------------------------------------------------- 1 | // bogosort adlah algoritma pengurutan yang berdasarkan 2 | // paradigma generate dan melakukan test. fungsi akan 3 | // menghasilkan permutasi dari inputnya sampai menemukan 4 | // satu yang diurutkan 5 | use crate::math::PCG32; 6 | use std::time::{SystemTime, UNIX_EPOCH}; 7 | 8 | const DEFAULT_VALUE: u64 = 4294967296; 9 | 10 | fn telah_disorting(arr: &[T], len: usize) -> bool { 11 | for i in 0..len - 1{ 12 | if arr[i] > arr[i + 1] { 13 | return false; 14 | } 15 | } 16 | true 17 | } 18 | 19 | #[cfg(target_pointer_width = "64")] 20 | fn generate_indeks(range: usize, generator: &mut PCG32) -> usize { 21 | generator.get_u64() as usize % range 22 | } 23 | 24 | #[cfg(not(target_pointer_width = "64"))] 25 | fn generate_indeks(range: usize, generator: &mut PCG32) -> usize { 26 | generator.get_u32() as usize % range 27 | } 28 | 29 | 30 | // menggunakan algoritma fisher-yates untuk menggenrte permutasi acak 31 | fn pemutasi_acak(arr: &mut [T], len: usize, generator: &mut PCG32) { 32 | for i in (1..len).rev() { 33 | let j = generate_indeks(i + 1,generator); 34 | arr.swap(i, j); 35 | } 36 | } 37 | 38 | pub fn bogo_sort(arr: &mut [T]) { 39 | let seed = match SystemTime::now().duration_since(UNIX_EPOCH) { 40 | Ok(duration) => duration.as_millis() as u64, 41 | Err(_) => DEFAULT_VALUE, 42 | }; 43 | 44 | let mut random_generator = PCG32::new_default(seed); 45 | let arr_length = arr.len(); 46 | 47 | while !telah_disorting(arr, arr_length) { 48 | permutasi_acak(arr, arr_length, &mut random_generator); 49 | } 50 | } 51 | 52 | #[cfg(test)] 53 | mod tests { 54 | use super::*; 55 | 56 | #[test] 57 | fn array_acak() { 58 | let mut arr = [1, 8, 3, 2, 7, 4, 6, 5]; 59 | bogo_sort(&mut arr); 60 | 61 | for i in 0..arr.len() - 1 { 62 | assert!(arr[i] <= arr[i + 1]); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /algorithms/src/sorting/mod.rs: -------------------------------------------------------------------------------- 1 | mod bogo_sort; 2 | 3 | pub use self::bogo_sort::bogo_sort; 4 | 5 | use std::cmp; 6 | 7 | pub fn telah_disorting(arr: &[T]) -> bool 8 | where T: cmp::PartialOrd, 9 | { 10 | if arr.is_empty() { 11 | return true; 12 | } 13 | let mut prev = &arr[0]; 14 | 15 | // fix this 16 | for item in arr.iter().skip(1) { 17 | if prev > item { 18 | return false; 19 | } 20 | prev = item; 21 | } 22 | true 23 | } 24 | 25 | #[cfg(test)] 26 | mod tests { 27 | #[test] 28 | fn is_sorted() { 29 | use super::*; 30 | 31 | assert!(telah_disorting(&[] as &[isize])); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /basics/01_introduction/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "introduction_bellshade" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["Rahman Hakim "] 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /basics/01_introduction/README.md: -------------------------------------------------------------------------------- 1 | - [Pengenalan Umum](#pengenalan-umum) 2 | - [Instalasi dan Cargo](#instalasi-dan-cargo) 3 | - [Instalasi Rust](#instalasi-rust) 4 | - [Cargo](#cargo) 5 | - [Pembuatan Package](#pembuatan-package) 6 | - [Kompilasi Program](#kompilasi-program) 7 | - [Cargo.toml](#cargotoml) 8 | - [Instalasi Package](#instalasi-package) 9 | 10 | 11 | 12 | ## Pengenalan Umum 13 | 14 | Rust merupakan bahasa pemrograman yang dikembangkan oleh Graydon Hoare dan disokong oleh [Mozilla Foundation](https://en.wikipedia.org/wiki/Mozilla_Foundation) pada 2014 lalu. Rust memaksakan keamanan memory yang berarti, semua _references_ pada Rust adalah valid, dan menunjuk pada memory yang benar-benar ada. Rust mencapainya tanpa penggunaan _garbage collector_. 15 | 16 | Bagi pemula, bahkan developer berpengalaman sekalipun, Rust akan terasa agak menyulitkan di awal. Berikut adalah struktur kode Rust: 17 | 18 | ```rust 19 | fn main() { 20 | // Entry Point 21 | } 22 | ``` 23 | 24 | 25 | ## Instalasi dan Cargo 26 | 27 | ### Instalasi Rust 28 | 29 | Cara Instalasi Rust yang paling umum adalah dengan menggunakan [rustup](https://rustup.rs/). Pada halaman web tersebut, OS kalian akan otomatis terdeteksi dan kalian dapat langsung mengikuti instruksi yang ada untuk menginstall Rust. Sebuah prompt akan bertanya pada anda tentang apa saja yang ingin anda install. 30 | 31 | Bila kalian telah menginstall Rust lewat rustup, jalankanlah command berikut untuk mengecek instalasi dan versi dari Rust dan Cargo: 32 | 33 | ``` 34 | cargo --version 35 | rustc --version 36 | ``` 37 | 38 | ### Cargo 39 | 40 | Cargo merupakan package manager Rust. Cargo digunakan untuk mulai dari pembuatan project, kompilasi, hingga pemasangan package. 41 | 42 | #### Pembuatan Package 43 | 44 | Tanpa membuat pusing, package disini dapat kalian artikan sebagai project. Ini adalah cara untuk membuat sebuah project baru pada Rust. Untuk membuat sebuah package baru pada Rust, kita dapat memilih apakah package kita akan menjadi sebuah library, atau binary. By default, cargo akan membuat sebuah binary package. Karena itu, untuk membuat sebuah library, dibutuhkan flag `--lib`. 45 | 46 | ``` 47 | cargo new --lib library_saya 48 | ``` 49 | 50 | Command diatas akan membuat sebuah package baru Rust dengan `lib.rs` didalamnya. Untuk membuat sebuah package binary, cukup buat sebuah project tanpa flag apapun, atau menggunakan `--bin` sebagai substitusi flag `--lib` diatas. 51 | 52 | ``` 53 | cargo new binary_saya 54 | ``` 55 | 56 | Project anda akan terstruktur seperti ini: 57 | 58 | ``` 59 | ├── Cargo.toml 60 | └── src 61 | └── main.rs 62 | ``` 63 | 64 | #### Kompilasi Program 65 | 66 | Untuk mengkompilasi seluruh project Rust, kita juga menggunakan cargo. Kita dapat membangun, mengecek error, ataupun langsung menjalankan sebuah project Rust bagi project binary. 67 | 68 | Setelah menjalankan perintah `cargo new binary_saya` diatas, akan ada sebuah program hello world pada `main.rs`. Kalian dapat menggunakannya untuk mengetes kompilasi Rust dengan menjalankan: 69 | 70 | ``` 71 | cargo run 72 | ``` 73 | 74 | Dan program akan langsung berjalan. Kalian juga dapat melakukan `build` tanpa menjalankannya langsung dengan command: 75 | 76 | ``` 77 | cargo build 78 | ``` 79 | 80 | Dan tergantung apakah build kalian merupakan `release` atau `debug`, package kalian akan berada dalam folder `target/`. 81 | 82 | Yang terakhir, dikarenakan kompilasi program pada Rust cukup lama, bila ingin mengecek error, kita tidak harus membangun project kita berulang kali. Kita cukup menggunakan 83 | 84 | ``` 85 | cargo check 86 | ``` 87 | 88 | Untuk mengecek apakah ada error berlangsung. 89 | 90 | #### Cargo.toml 91 | 92 | File `Cargo.toml` merupakan sebuah file yang berisi semua detail tentang project kita mulai dari nama package, versi package, hingga nama author. Pada file `Cargo.toml` jugalah kita menuliskan nama-nama package dan versi package yang kita inginkan. 93 | 94 | 95 | #### Instalasi Package 96 | 97 | Untuk menginstall package yang ingin kita gunakan pada sebuah project Rust, kita menuliskannya di `Cargo.toml`. Penulisannya ada dibawah tag `[dependencies]` seperti berikut: 98 | 99 | ```toml 100 | [dependencies] 101 | serde = "1.0.140" 102 | ``` 103 | 104 | Dan package akan terinstall ketika kita menjalankan `build` atau `run`. Yang kedua adalah, bila kita ingin menginstall package binary, maka kita dapat menggunakan command `cargo install` seperti berikut: 105 | 106 | ``` 107 | cargo install diesel_cli 108 | ``` -------------------------------------------------------------------------------- /basics/01_introduction/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | #[test] 4 | fn intro() { 5 | let a = "Bellshade!"; 6 | 7 | println!("Hello World!"); 8 | println!("Hello {}", a); 9 | 10 | assert_eq!("Bellshade!", a); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /basics/02_variables/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "variables_bellshade" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["Rahman Hakim "] 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /basics/02_variables/README.md: -------------------------------------------------------------------------------- 1 | # Variables 2 | 3 | Variabel merupakan tempat penyimpanan data sementara. Pada Bahasa Rust, semua variabel secara default bersifat immutable atau tidak dapat dirubah. Untuk membuatnya menjadi mutable, dibutuhkan keyword `mut`. Rust memiliki tiga cara untuk mendeklarasikan sebuah variabel. 4 | 5 | ### Constant Variables 6 | 7 | Constant Variables atau variabel konstan merupakan sebuah variabel yang tidak akan dapat diubah nilainya. Ia akan tetap, sampai kapan pun. Biasanya, konstanta digunakan untuk variabel global yang tidak memiliki kemungkinan untuk dapat berubah. Pendeklarasian variabel konstan diawali dengan tanda `const`, diikuti dengan tipe data yang digunakan. 8 | 9 | Contoh dari sebuah variabel konstan adalah sebagai berikut: 10 | 11 | ```rust 12 | const PI: f32 = 3.14; 13 | 14 | fn main() { 15 | // Kode 16 | } 17 | ``` 18 | 19 | ### Static Variables 20 | 21 | Static Variables atau variabel statis merupakan tipe variabel yang sangat mirip dengan konstanta. Namun, Static variable masih memiliki kemungkinan untuk diubah, atau `mutable`. Static variable memiliki lifetime `'static` dan masih dapat diubah menggunakan block `unsafe`. Static variable ditandai dengan keyword `static`. 22 | 23 | ```rust 24 | 25 | // Mutable Static Variable 26 | static mut COUNTER: u32 = 0; 27 | 28 | // Immutable static variable 29 | static VAR: u32 = 0; 30 | 31 | fn main() { 32 | // Kode 33 | } 34 | ``` 35 | 36 | ### Local Variables 37 | 38 | Local variables atau variabel lokal merupakan tipe variabel yang paling umum. Local variable ini aman untuk dimutate menggunakan `mut` tanpa perlu menggunakan block `unsafe`. Penggunaan local variable ini hanya dalam scopenya saja dan tidak dapat digunakan secara global. Local variable ditandai dengan keyword `let`, dan tidak membutuhkan tipe data didefinisikan secara eksplisit. 39 | 40 | 41 | ```rust 42 | fn main() { 43 | // Mutable 44 | let mut x = 5; 45 | x += 10; 46 | println!("{}", x); 47 | 48 | // Immutable 49 | let a = 10; 50 | println!("{}", a); 51 | } 52 | ``` 53 | 54 | # Latihan 55 | 56 | Pada bab 2 ini, kalian dapat melihat file `lib.rs` didalam folder `src`. Didalam sana, ada sebuah test dengan variabel berikut: 57 | 58 | ```rust 59 | let a = todo!(); 60 | ``` 61 | 62 | Gantilah macro `todo!` tersebut dengan nilai yang sesuai pada macro `assert_eq!`, kemudian jalankan `cargo test` untuk mengetes apakah jawaban kalian sudah benar atau belum. 63 | 64 | Contoh cara mengerjakan: 65 | 66 | ```rust 67 | // Sebelum 68 | 69 | let a = todo!(); 70 | 71 | assert_eq!(20, a + 5); 72 | 73 | // Sesudah 74 | 75 | let a = 15; 76 | 77 | assert_eq!(20, a + 5); 78 | 79 | // cargo test 80 | 81 | running 1 test 82 | test tests::start ... ok 83 | 84 | test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s 85 | ``` 86 | -------------------------------------------------------------------------------- /basics/02_variables/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | #[test] 4 | fn start() { 5 | let a = todo!(); 6 | 7 | assert_eq!("Hello World", a); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /basics/03_functions/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "functions_bellshade" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["Rahman Hakim "] 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /basics/03_functions/README.md: -------------------------------------------------------------------------------- 1 | Fungsi merupakan sebuah abstraksi dari proses khusus yang kemudian mengembalikan nilai. Sebuah prosedur merupakan sebuah abstraksi rangkaian dari proses khusus yang serupa dengan fungsi, namun prosedur tidak mengembalikan apa-apa. 2 | 3 | ## Fungsi dalam Rust 4 | 5 | Dalam bahasa Rust, fungsi dideklarasikan dengan keyword `fn`. 6 | 7 | ```rust 8 | 9 | // Sebuah fungsi 10 | fn a_function() -> i32 { 11 | 0 12 | } 13 | 14 | // Sebuah fungsi dengan parameter 15 | fn a_function_with_param(a: i32) -> i32 { 16 | a + 1 17 | } 18 | ``` 19 | 20 | Saat sebuah fungsi mengembalikan nilai, kita harus menuliskan secara eksplisit tipe apa yang ia kembalikan dengan tanda `->`. Lalu karena Rust merupakan _expression-oriented language_, kita tidak perlu menuliskan keyword `return` pada saat mengembalikan nilai. Cukup menuliskan nilai yang dikembalikan tanpa titik koma seperti yang terlihat diatas. 21 | 22 | ### Early Return 23 | 24 | Ada kalanya kita menginginkan sebuah nilai untuk dikembalikan duluan sebelum proses sebuah fungsi berakhir. Disinilah kita menggunakan keyword `return` dengan benar pada Rust. 25 | 26 | ```rust 27 | fn early_return(a: i32) -> i32 { 28 | if a > 10 { 29 | return 20; 30 | } 31 | 32 | a + 3 33 | } 34 | ``` 35 | 36 | Fungsi diatas akan mengembalikan `20` bila parameter `a` lebih besar dibanding `10`, dan mengabaikan `a + 3` dibawahnya. 37 | 38 | ## Prosedur dalam Rust 39 | 40 | Disini kita akan membahas lebih detail tentang prosedur. Sebuah prosedur biasanya adalah sebuah rangkaian dari proses khusus yang dijalankan untuk mengubah sesuatu, dan tidak mengembalikan apa-apa. Contoh dari sebuah prosedur untuk mengubah nilai sebuah variabel adalah berikut: 41 | 42 | ```rust 43 | fn change_value(a: &mut i32) { 44 | *a += 3; 45 | } 46 | 47 | fn main() { 48 | let mut a = 5; 49 | // 5 50 | println!("Sebelum prosedur: {}", a); 51 | change_value(&mut a); 52 | // 8 53 | println!("Setelah prosedur: {}", a); 54 | } 55 | ``` 56 | 57 | Prosedur tersebut menerima sebuah _mutable reference_ atau reference yang dapat diubah kepada sebuah nilai, dan kemudian mengubah nilai tersebut. Tanda asterisk (*) diatas berfungsi untuk men-dereference nilai dari reference diatas, sehingga ia mengubah nilai yang direferensikan reference tersebut. 58 | 59 | # Latihan 60 | 61 | Seperti pada materi sebelumnya, kerjakan latihan yang berada pada file `lib.rs` dalam folder `src`. Ganti `todo!()` dengan jawabanmu, kemudian jalankan `cargo test`! -------------------------------------------------------------------------------- /basics/03_functions/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | 4 | fn multiply(a: i32, b: i32) -> i32 { 5 | todo!() 6 | } 7 | 8 | #[test] 9 | fn start() { 10 | let a = 10; 11 | let b = 20; 12 | 13 | let mult = multiply(a, b); 14 | 15 | assert_eq!(200, mult); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /basics/04_ownership/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ownership_bellshade" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["Rahman Hakim "] 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /basics/04_ownership/README.md: -------------------------------------------------------------------------------- 1 | # Ownership 2 | 3 | Ownership merupakan sebuah sistem yang membuat Rust itu unik. Disinilah nilai jual Rust, yang membedakan Rust dan bahasa lainnya. Dengan sistem ownership pada Rust, Rust dapat mencapai keamanan memory tanpa penggunaan garbage collector. 4 | 5 | ## Peraturan Ownership 6 | 7 | Ownershipnya Rust memiliki beberapa peraturan: 8 | 9 | - Setiap nilai dalam Rust memiliki pemilik atau owner 10 | - Hanya boleh ada satu pemilik dalam satu waktu 11 | - Ketika si pemilik keluar dari scope, maka nilai akan di-drop atau dihapus 12 | 13 | ## Masalah 14 | 15 | Bagi pemula, saat belajar Rust pasti pernah melakukan hal semacam ini: 16 | 17 | ```rust 18 | fn main() { 19 | let a = String::from("Halo"); 20 | let b = a; 21 | 22 | println!("{}", a); 23 | } 24 | ``` 25 | 26 | Dan kemudian, saat dicompile, ternyata error. 27 | 28 | ``` 29 | error[E0382]: borrow of moved value: `a` 30 | --> test.rs:5:20 31 | | 32 | 2 | let a = String::from("Halo"); 33 | | - move occurs because `a` has type `String`, which does not implement the `Copy` trait 34 | 3 | let b = a; 35 | | - value moved here 36 | 4 | 37 | 5 | println!("{}", a); 38 | | ^ value borrowed here after move 39 | ``` 40 | 41 | Hal ini tentu akan memusingkan bagi pemula yang baru belajar bahasa Rust. Apa sih yang dimaksud dengan `moved value`? Apa sih `borrow`? Apa maksud dari `does not implement Copy trait`? Untuk mengetahuinya, mari kita kupas lebih lanjut. 42 | 43 | 44 | ### Primitive Type vs Non-Primitive Type 45 | 46 | Tipe data primitif merupakan tipe data klasik seperti `i32`, `u32`, `bool`, `f32`, dan semacamnya sedangkan tipe data non-primitif merupakan tipe data seperti `String` atau `Vec`. Kunci utama dari ownership pada kedua jenis tipe data ini adalah, tipe data primitif mengimplementasikan trait `Copy` sedangkan tipe data non-primitif tidak. 47 | 48 | ### Copy Trait 49 | 50 | Copy trait merupakan sebuah `trait` (Akan dibahas lebih lanjut nanti) yang berfungsi untuk memberitahu compiler bahwa suatu tipe tidak akan me-move nilainya, melainkan mengkopinya. 51 | 52 | ```rust 53 | fn main() { 54 | let a = 10; 55 | let b = a; 56 | 57 | println!("{}", a); 58 | println!("{}", b); 59 | } 60 | ``` 61 | 62 | Kode diatas akan berjalan dengan sempurna, tidak seperti kode dengan tipe data `String` diatasnya lagi. Mengapa? Karena `a` dan `b` memiliki tipe data `i32`, maka `a` dan `b` mengimplementasikan trait `Copy` sehingga variabel `a` yang di-assign ke `b` akan menunjuk kepada nilai yang berbeda di memory. `a` yang di-assign pada `b` bukan merupakan variabel `a` yang sama lagi, melainkan nilai `10` baru di memory karena `a` merupakan sebuah kopi baru dari nilai `10` tersebut. Hal ini dilakukan secara otomatis untuk semua tipe yang mengimplementasikan trait `Copy`. Kesimpulannya adalah, tipe yang mengimplementasikan trait `Copy` akan secara otomatis membuat nilai baru di memory setiap kali ia dipakai. 63 | 64 | ### Move 65 | 66 | Kita telah mengetahui bahwa tipe yang mengimplementasikan `Copy` akan selalu membuat kopi dari dirinya sendiri setiap kali ia dipakai. Lalu bagaimana dengan move? Tipe yang tidak mengimplementasikan `Copy` akan di-move, bukan di-copy setiap kali ia dipakai. Untuk lebih jelasnya, mari kita lihat kembali kode yang error diatas. 67 | 68 | ```rust 69 | fn main() { 70 | let a = String::from("Halo"); 71 | let b = a; 72 | 73 | println!("{}", a); 74 | } 75 | ``` 76 | 77 | Disini terlihat bahwa variabel `a` di-assign pada variabel `b`, kemudian kita memakai variabel `a` pada macro `println!` dibawahnya. Dan yang terjadi adalah error. Mengapa demikian? Karena disaat kita assign variabel `a` pada `b`, kepemilikan akan nilai dari variabel `a`, yaitu `String "Halo"` akan di-move ke variabel `b` sehingga `a` tidak memiliki kepemilikan, atau ownership terhadap `String "Halo"` lagi yang membuatnya tidak lagi valid. Hal ini merupakan salah satu dari peraturan ownership dimana hanya dapat ada satu pemilik atau owner dalam satu waktu. `a` yang di-assign ke `b` bukan merupakan kopi, namun merupakan nilai yang sama di memory. 78 | 79 | ## Mengatasi Masalah 80 | 81 | Lalu bagaimana cara kita untuk mengatasi masalah diatas? Bagaimana cara kita memakai variabel `a` berulang kali tanpa membuat error? Ada dua cara. 82 | 83 | ### Borrow 84 | 85 | Dalam Rust, borrow atau meminjam merupakan sebuah cara yaitu menaruh _ampersand_ (&) atau tanda reference di depan sebuah variabel. Dengan borrow, kepemilikan sebuah nilai hanya akan dipinjam, bukan di-move dan akan dikembalikan kepada pemilik asalnya setelah keluar dari scope. 86 | 87 | ```rust 88 | fn main() { 89 | let a = String::from("Halo"); 90 | let b = &a; 91 | 92 | println!("{}", a); 93 | println!("{}", b); 94 | } 95 | ``` 96 | 97 | Kode diatas akan berjalan dengan baik. Kita juga dapat memberikan reference pada fungsi lewat parameter. 98 | 99 | ```rust 100 | fn greet(name: &String) { 101 | println!("Halo {}", name); 102 | } 103 | 104 | fn main() { 105 | let name = String::from("Rahman"); 106 | greet(&name); 107 | } 108 | ``` 109 | 110 | Dan variabel `name` dapat tetap dipakai walau fungsi dipanggil berulang kali. 111 | 112 | ### Clone 113 | 114 | Cara kedua adalah cloning. Dengan menggunakan trait `Clone`, kita dapat melakukan hal yang mirip `Copy`, namun secara eksplisit kepada suatu tipe yang mengimplementasikan `Clone`. 115 | 116 | ```rust 117 | fn main() { 118 | let a = String::from("Halo"); 119 | let b = a.clone(); 120 | 121 | println!("{}", a); 122 | println!("{}", b); 123 | } 124 | ``` 125 | 126 | Dan kode diatas akan berjalan dengan baik. Namun, perlu diketahui bahwa `Clone` itu "mahal", karena `Clone` akan membuat nilai yang baru di memory, dan memory yang dialokasikan oleh tipe non-primitif tidaklah kecil. Jadi `a` dan `b` diatas tidaklah menunjuk kepada nilai yang sama di memory. 127 | 128 | # Latihan 129 | 130 | Pada latihan kali ini, kita akan bertemu dengan dua fungsi. Namun, kedua-duanya error. Apa yang menyebabkannya? Tugas kalian adalah memperbaiki kode tersebut. Kerjakanlah soal pada file `lib.rs` dalam folder `src` kemudian run `cargo test` untuk mengecek jawaban kalian! -------------------------------------------------------------------------------- /basics/04_ownership/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | 4 | fn greet(name: String) -> String { 5 | format!("Selamat Datang di {}", name) 6 | } 7 | 8 | fn greet_2(name: String) -> String { 9 | format!("Semoga mendapat ilmu di {}", name) 10 | } 11 | 12 | #[test] 13 | fn start() { 14 | let a = String::from("Bellshade"); 15 | 16 | assert_eq!("Selamat Datang di Bellshade", greet(a)); 17 | 18 | assert_eq!("Semoga mendapat ilmu di Bellshade", greet_2(a)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /basics/05_enumeration_dan_pattern_matching/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "enumeration_dan_pattern_matching" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /basics/05_enumeration_dan_pattern_matching/README.md: -------------------------------------------------------------------------------- 1 | # Enumeration 2 | 3 | Enumeration atau dikenal juga dengan `enum` merupakan sebuah `type` dalam Rust. Berbeda dengan bahasa lain, enumerasi dalam Rust dapat menyimpan nilai. Fitur ini membuat enumerasi dalam Rust sangat powerful dan dapat digunakan untuk banyak hal, salah satunya adalah untuk `Result` type dan `Option` type pada Rust. 4 | 5 | ## Deklarasi 6 | 7 | Pendeklarasian `enum` dalam Rust menggunakan keyword `enum`. 8 | 9 | ```rust 10 | enum Payment { 11 | Cash, 12 | Credit, 13 | Debit, 14 | } 15 | ``` 16 | 17 | Tiap enumerasi didalam sebuah `enum` disebut juga dengan varian. Kita dapat menggunakan varian dalam `enum` dibawah satu tipe `enum` yang sama sehingga kita dapat menggunakan salah satu dari beberapa tipe dibawah satu tipe utama. 18 | 19 | Untuk kondisional tentang varian apa yang kita gunakan, kita dapat menggunakan pattern matching. 20 | 21 | ## Pattern Matching 22 | 23 | Pattern Matching merupakan salah satu fitur utama dalam Rust. Pattern matching ini mirip dengan `switch` pada bahasa lain, namun pattern matching dapat digunakan untuk lebih banyak hal seperti digunakan pada assignment. Pattern matching harus bersifat _exhaustive_ atau mengcover semua kemungkinan yang ada. 24 | 25 | Pattern matching menggunakan keyword `match`. 26 | 27 | ```rust 28 | fn main() { 29 | let a = 10; 30 | 31 | match a { 32 | 10 => println!("A adalah 10"), 33 | 20 => println!("A adalah 20"), 34 | _ => println!("A adalah lainnya"), 35 | } 36 | } 37 | ``` 38 | 39 | Pada kode diatas, tanda underscore `_` menandakan value lain bila `a` bukanlah merupakan salah satu dari kedua kemungkinan diatas, yaitu `10` dan `20`. 40 | 41 | ### Pattern Matching pada Enum 42 | 43 | Sekarang kita akan membahas penggunaan pattern matching pada `enum` yang telah kita buat diatas. Pertama-tama lihatlah kode ini. 44 | 45 | ```rust 46 | enum Payment { 47 | Cash, 48 | Credit, 49 | Debit, 50 | } 51 | 52 | fn main() { 53 | let payment = Payment::Cash; 54 | 55 | match payment { 56 | Payment::Cash => println!("Pembayaran dengan Cash"), 57 | Payment::Debit => println!("Pembayaran dengan Debit"), 58 | Payment::Credit => println!("Pembayaran dengan Credit"), 59 | } 60 | } 61 | ``` 62 | 63 | Kita menggunakan varian `Cash` pada variabel `payment`, kemudian mencocokkannya dengan `match`. Hasil yang akan keluar tentulah `Pembayaran dengan Cash`. Sekarang, agar lebih jelas dan reusable, kita akan menggunakan prosedur. 64 | 65 | ```rust 66 | enum Payment { 67 | Cash, 68 | Credit, 69 | Debit, 70 | } 71 | 72 | fn pay(method: &Payment) { 73 | match method { 74 | Payment::Cash => println!("Membayar dengan tunai"), 75 | Payment::Credit => println!("Membayar dengan credit"), 76 | Payment::Debit => println!("Membayar dengan debit") 77 | } 78 | } 79 | 80 | fn main() { 81 | let payment = Payment::Cash; 82 | let payment2 = Payment::Credit; 83 | let payment3 = Payment::Debit; 84 | 85 | pay(&payment); 86 | pay(&payment2); 87 | pay(&payment3); 88 | } 89 | ``` 90 | 91 | Dan sekarang, kita dapat menggunakan satu fungsi untuk semua varian. 92 | 93 | Sekarang, kita akan menambahkan nilai kedalam varian. Untuk melakukannya, kita harus menaruh tipe data di dalam varian. 94 | 95 | ```rust 96 | enum Payment { 97 | Cash(f64), 98 | Credit(String, f64), 99 | Debit(String, f64), 100 | } 101 | ``` 102 | 103 | Dengan melakukan hal ini, kita dapat memasukkan nilai sesuai dengan tipe data yang ada kedalam varian. Sekarang, kita akan merombak ulang yang telah kita tulis. 104 | 105 | ```rust 106 | enum Payment { 107 | Cash(f64), 108 | Credit(String, f64), 109 | Debit(String, f64), 110 | } 111 | 112 | fn pay(method: &Payment) { 113 | match method { 114 | Payment::Cash(amount) => println!("Membayar dengan tunai sebesar {}", amount), 115 | Payment::Credit(num, amount) => println!("Membayar dengan credit dengan nomor {} sebesar {}", num, amount), 116 | Payment::Debit(num, amount) => println!("Membayar dengan debit dengan nomor {} sebesar {}", num, amount), 117 | } 118 | } 119 | 120 | fn main() { 121 | let cash = Payment::Cash(100000.0); 122 | let credit = Payment::Credit(String::from("123"), 50000.0); 123 | let debit = Payment::Debit(String::from("234"), 75000.0); 124 | 125 | pay(&cash); 126 | pay(&credit); 127 | pay(&debit) 128 | } 129 | ``` 130 | 131 | Lihatlah, kita dapat memasukkan nilai kedalam varian diatas. Kemudian, kita dapat mengecek nilai tersebut dengan menggunakan variabel didalam pattern matching. Variabel-variabel tersebut pada kode diatas adalah `num` dan `amount`. Mereka mewakili nilai yang kalian berikan. Sekarang, output yang keluar adalah sebagai berikut: 132 | 133 | ``` 134 | Membayar dengan tunai sebesar 100000 135 | Membayar dengan credit dengan nomor 123 sebesar 50000 136 | Membayar dengan debit dengan nomor 234 sebesar 75000 137 | ``` 138 | 139 | Bagaimana? Sangat berguna bukan? Kita dapat menggunakan salah satu dari beberapa varian dibawah satu tipe utama, yang dalam kasus diatas adalah `Payment`. 140 | 141 | ## Penggunaan Pattern Matching dalam assignment 142 | 143 | Kita dapat assign variabel secara kondisional dengan pattern matching seperti ini contohnya: 144 | 145 | ```rust 146 | fn main() { 147 | let pay = Payment::Credit(String::from("555"), 100000.0); 148 | 149 | // Diskon bila memakai credit dan debit 150 | let calculate = match pay { 151 | Payment::Cash(amount) => amount, 152 | Payment::Credit(num, amount) => amount - ((amount * 50.0) / 100.0), 153 | Payment::Debit(num, amount) => amount - ((amount * 40.0) / 100.0), 154 | }; 155 | 156 | println!("{}", calculate); 157 | 158 | } 159 | ``` 160 | 161 | Nilai `calculate` akan tergantung pada varian yang kita gunakan. 162 | 163 | # Latihan 164 | 165 | TODO -------------------------------------------------------------------------------- /basics/05_enumeration_dan_pattern_matching/src/example.rs: -------------------------------------------------------------------------------- 1 | pub fn pattern_match() { 2 | let kata = "bakso"; 3 | // pattern matching string 4 | match kata { 5 | "bakso" => println!("bakso"), 6 | "burger" => println!("burger"), 7 | _ => println!("pilihan selain di atas"), 8 | } 9 | 10 | let angka = 100; 11 | // pattern matching angka, bisa integer, float, dll 12 | match angka { 13 | 100 => println!("ini angka 100"), 14 | 200 => println!("ini angka 200"), 15 | _ => println!("pilihan selain di atas"), 16 | } 17 | 18 | let boolean = true; 19 | // pattern matching boolean 20 | match boolean { 21 | true => println!("ini true"), 22 | false => println!("ini false"), 23 | } 24 | } 25 | // setelah memahami pattern matching 26 | // kita masuk ke enum type 27 | enum Animal { 28 | Dog, 29 | Cat, 30 | Fish 31 | } 32 | // penggunaan dasar enum 33 | pub fn basic_enum() { 34 | // buat variable dengan enum type dog 35 | let dog = Animal::Dog; 36 | // pattern match enum 37 | // masing masing pattern return string 38 | let result = match dog { 39 | Animal::Dog => "dog".to_string(), 40 | Animal::Cat => "cat".to_string(), 41 | Animal::Fish => "fish".to_string(), 42 | }; 43 | // cek jika return sesuai pattern match 44 | assert_eq!(result, "dog"); 45 | 46 | let cat = Animal::Cat; 47 | // pattern match cat 48 | let result = match cat { 49 | Animal::Dog => "dog".to_string(), 50 | Animal::Cat => "cat".to_string(), 51 | Animal::Fish => "fish".to_string(), 52 | }; 53 | // cek jika return sesuai pattern match 54 | assert_eq!(result, "cat"); 55 | 56 | let fish = Animal::Fish; 57 | // pattern match cat 58 | let result = match fish { 59 | Animal::Dog => "dog".to_string(), 60 | Animal::Cat => "cat".to_string(), 61 | Animal::Fish => "fish".to_string(), 62 | }; 63 | // cek jika return sesuai pattern match 64 | assert_eq!(result, "fish"); 65 | } 66 | // setelah memahami enum 67 | // enum bisa menampung suatu nilai dengan type yang di tentukan 68 | // ini bisa di sebut sebagai enum states 69 | enum Person { 70 | Budi(String), 71 | Andi(String, i8), 72 | Anton(String, i8, f64), 73 | } 74 | // penggunaan enum states 75 | pub fn enum_state() { 76 | // deklarasi budi dengan enum states yang telah di definisikan 77 | let budi = Person::Budi("polisi".to_string()); 78 | // pattern match budi 79 | let pekerjaan_budi = match budi { 80 | Person::Budi(pekerjaan) => { 81 | println!("pekerjaan budi adalah: {}", pekerjaan); 82 | pekerjaan 83 | }, 84 | Person::Andi(pekerjaan, umur) => pekerjaan, 85 | Person::Anton(pekerjaan, umur, gaji) => pekerjaan, 86 | }; 87 | // validasi pekerjaan budi 88 | assert_eq!(pekerjaan_budi, "polisi"); 89 | 90 | // deklarasi andi dengan enum states yang telah di definisikan 91 | let andi = Person::Andi("programmer".to_string(), 25); 92 | // pattern match andi 93 | let umur_andi = match andi { 94 | Person::Andi(pekerjaan, umur) => { 95 | println!("umur andi adalah: {}", umur); 96 | umur 97 | }, 98 | Person::Anton(pekerjaan, umur, gaji) => umur, 99 | _ => 0 100 | }; 101 | // validasi umut andi 102 | assert_eq!(umur_andi, 25); 103 | 104 | // deklarasi anton dengan enum states yang telah di definisikan 105 | let anton = Person::Anton("dokter".to_string(), 30, 25000000.00); 106 | // pattern match anton 107 | let gaji_anton = match anton { 108 | Person::Anton(pekerjaan, umur, gaji) => { 109 | println!("gaji anton adalah: {}", gaji); 110 | gaji 111 | }, 112 | _ => 0.0 113 | }; 114 | // validasi gaji anton 115 | assert_eq!(gaji_anton, 25000000.00); 116 | } 117 | // setelah memahami enum states 118 | // kita bisa membuat enum method dan mengakses states tersebut 119 | enum Tebakan { 120 | Kalah(String), 121 | Menang(String), 122 | } 123 | // deklarasi enum method di dalam keyword impl 124 | impl Tebakan { 125 | // method dari enum tebakan 126 | fn tebak(&self) -> String { 127 | match self { 128 | Tebakan::Kalah(reason_kalah) => { 129 | println!("Tebakan salah: {}", reason_kalah); 130 | reason_kalah.to_string() 131 | } 132 | Tebakan::Menang(reason_menang) => { 133 | println!("Tebakan benar: {}", reason_menang); 134 | reason_menang.to_string() 135 | } 136 | } 137 | } 138 | } 139 | // penggunaan enum method 140 | pub fn enum_method() { 141 | // buatkan enum type kalah dengan alasan kalah sebagai enum state 142 | let pilihan = Tebakan::Kalah("ini alasan tebakan mu salah".to_string()); 143 | // panggile enum method 144 | let alasan = pilihan.tebak(); 145 | // validasi alasan 146 | assert_eq!(alasan, "ini alasan tebakan mu salah"); 147 | 148 | // buatkan enum type menang dengan alasan menang sebagai enum state 149 | let pilihan = Tebakan::Menang("ini alasan tebakan mu menang".to_string()); 150 | // panggil enum method 151 | let alasan = pilihan.tebak(); 152 | // validasi alasan 153 | assert_eq!(alasan, "ini alasan tebakan mu menang"); 154 | } 155 | 156 | -------------------------------------------------------------------------------- /basics/05_enumeration_dan_pattern_matching/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod example; 2 | 3 | #[cfg(test)] 4 | mod tests { 5 | use crate::example; 6 | 7 | #[test] 8 | fn example_test() { 9 | example::pattern_match(); 10 | example::basic_enum(); 11 | example::enum_state(); 12 | example::enum_method(); 13 | } 14 | 15 | #[test] 16 | fn start() {} 17 | } -------------------------------------------------------------------------------- /basics/06_result_dan_option_type/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "result_dan_option_type" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /basics/06_result_dan_option_type/README.md: -------------------------------------------------------------------------------- 1 | # Result dan Option Type 2 | 3 | Pada Rust, kita tidak melakukan error handling secara konvensional dengan try-catch seperti bahasa lainnya, melainkan menggunakan sebuah tipe yang diambil dari bahasa pemrograman fungsional, yaitu `Result` type. Untuk `Option` type, Rust menggunakannya sebagai tipe yang merepresentasikan value yang dapat kosong atau `null`. Rust menerapkan `null safety` atau keamanan dari `null` dengan menggunakan `Option`. 4 | 5 | ## Result Type 6 | 7 | Pada Rust, Result type diimplementasikan menggunakan `enum`. Deklarasi `Result` type pada standard library `std::result` adalah sebagai berikut: 8 | 9 | ```rust 10 | enum Result { 11 | Ok(T), 12 | Err(E), 13 | } 14 | ``` 15 | 16 | `T` dan `E` diatas merupakan tipe generic dimana kita dapat memasukkan tipe apapun kedalam sana dimana `T` merupakan tipe dari nilai yang akan dikembalikan dan `E` merupakan tipe dari error yang dapat muncul. Untuk generics akan kita bahas lebih dalam nanti. Sekarang, kita berfokus terlebih dahulu pada `Result` type. 17 | 18 | Sekarang, kita akan melihat contoh penggunaan `Result`. Misalnya, kita memiliki file `tes.txt` dengan isi teks "Halo", lalu kita ingin membukanya. 19 | 20 | ```rust 21 | use std::fs::File; 22 | use std::io::Read; 23 | 24 | fn main() { 25 | let mut file = match File::open("tes.txt") { 26 | Ok(f) => f, 27 | Err(_) => panic!("File tidak dapat dibuka"), 28 | }; 29 | let mut contents = String::new(); 30 | match file.read_to_string(&mut contents) { 31 | Ok(_) => {}, 32 | Err(e) => panic!("Tidak dapat membaca konten {}", e) 33 | }; 34 | println!("{}", contents); 35 | } 36 | ``` 37 | Nah, seperti yang pernah dijelaskan, kita bisa memakai `match` untuk `enum` adan karena `Result` type merupakan `enum`, kita bisa me-handle error dengan menggunakan `match`. `Ok` akan me-wrap atau membungkus value yang ingin kita dapatkan, sedangkan `Err` akan membungkus value error. Untuk ignore atau mengabaikan variabel yang di-wrap dalam varian, kita dapat menggunakan underscore `_`. Pada kode diatas, bila file `tes.txt` ditemukan, maka variabel `f` yang merupakan tipe `File` akan di-assign ke `file`. Sedangkan bila file tidak ditemukan, maka program akan terhenti diakrenakan `panic`. Untuk baris `match file.read_to_string(&mut contents)`, kita menggunakan underscore didalam `Ok` dan kemudian tidak mengembalikan apa-apa, kita hanya mengisinya dengan kurung kurawal kosong `{}` dikarenakan method `read_to_string` akan mengisi variabel lain sehingga nilai dari `read_to_string` itu sendiri tidak dibutuhkan. Kita juga dapat menggunakan error yang terjadi dengan memakai variabel yang di-wrap dalam `Err` seperti `e` diatas. 38 | 39 | Cara diatas sangatlah _awkward_ dan terlalu berputar-putar. Seharusnya kita tidak perlu menggunakan `match` dan kemudian memasang `panic` seperti itu. Error handling dengan `match` harusnya dilakukan disaat kita harus me-handle error dengan lebih detail lagi, seperti mengembalikan value errornya dari fungsi, fallback ketika error terjadi, dan sebagainya. Untuk mengatasi hal seperti diatas, Rust memiliki beberapa method yang lebih praktis. 40 | 41 | ### Unwrap 42 | 43 | `unwrap` adalah sebuah method Rust dimana kita langsung mengambil variabel yang di-wrap didalam `Ok` pada `Result` type dan akan terjadi `panic` bila `Err` terjadi. Kita sebaiknya memakai `unwrap` bila sudah yakin bahwa error tidak akan terjadi dan pasti value `Ok` yang akan dikembalikan. Namun sebagai contoh, kita akan memakai unwrap untuk kode diatas. 44 | 45 | ```rust 46 | use std::fs::File; 47 | use std::io::Read; 48 | 49 | fn main() { 50 | let mut file = File::open("tes.txt").unwrap(); 51 | let mut contents = String::new(); 52 | file.read_to_string(&mut contents).unwrap(); 53 | println!("{}", contents); 54 | } 55 | ``` 56 | 57 | Nah, kode ini hampir ekuivalen dengan kode diatas. Namun, ada cara method lain yang akan membuat kode sependek menggunakan `unwrap`, namun ekuivalen dengan kode diatas. 58 | 59 | 60 | ### Expect 61 | 62 | Dengan menggunakan method `expect`, kita dapat menuliskan error kita sendiri bila `panic` terjadi. `expect` tidaklah berbeda dengan `unwrap`, ia akan langsung mengambil value dalam `Ok`, atau `panic`. Namun, kita dapat menuliskan error kita sendiri. 63 | 64 | ```rust 65 | use std::fs::File; 66 | use std::io::Read; 67 | 68 | fn main() { 69 | let mut file = File::open("tes.txt").expect("Tidak dapat membuka file"); 70 | let mut contents = String::new(); 71 | file.read_to_string(&mut contents).expect("Tidak dapat membaca file"); 72 | println!("{}", contents); 73 | } 74 | ``` 75 | 76 | Kode ini ekuivalen dengan kode yang menggunakan `match` diatas. Bila `panic` terjadi, maka seperti kita menggunakan macro `panic!`, pesan yang akan keluar adalah pesan yang kita tuliskan sendiri. 77 | 78 | ### `?` Operator 79 | 80 | Rust memiliki sebuah operator khusus yang bisa digunakan untuk me-handle error dengan lebih praktis dan aman, yaitu dengan operator `?`. Namun, untuk menggunakan operator `?`, return type dari sebuah fungsi haruslah `Result` atau `Option` type, dan bagi `Result`, error yang ada pada `Result` type yang dikembalikan harus mengimplementasikan `From` yang akan kita bahas lebih lanjut nanti. 81 | 82 | ```rust 83 | use std::fs::File; 84 | use std::io::{self, Read}; 85 | 86 | fn main() -> io::Result<()> { 87 | let mut file = File::open("tes.txt")?; 88 | let mut contents = String::new(); 89 | file.read_to_string(&mut contents)?; 90 | println!("{}", contents); 91 | 92 | // Agar sesuai return type 93 | Ok(()) 94 | } 95 | ``` 96 | 97 | Kode diatas merupakan contoh penggunaan `?` operator. `?` operator akan mengembalikan `Err` bila error terjadi, dan akan me-assign value yang berada dalam `Ok` kepada variabel yang bersangkutan dengan operator `?`. Jadi ia berfungsi sama seperti semua method diatas, namun tidak `panic` bila error terjadi, tapi mengembalikan error tersebut. Untuk assignment value, ia akan assign seperti biasa bila sukses sehingga membuatnya ekuivalen dengan hal seperti ini: 98 | 99 | ```rust 100 | let mut file = match File::open("tes.txt") { 101 | Ok(f) => f, 102 | Err(e) => return Err(e), 103 | }; 104 | ``` 105 | 106 | ## Option Type 107 | 108 | `Option` type adalah enum, yang digunakan untuk null-safety dimana kita memakainya bila suatu value dapat memiliki kemungkinan untuk kosong. Definisi `Option` adalah sebagai berikut: 109 | 110 | ```rust 111 | pub enum Option { 112 | None, 113 | Some(T), 114 | } 115 | ``` 116 | 117 | Varian `Some(T)` merupakan varian yang berisi value bila value tersebut ada, dimana `T` adalah tipe generic yang mana value apapun dapat masuk kesana sedangkan `None` merupakan varian bila value tersebut kosong. Namun dengan `Option` type, value tersebut bukanlah `null`, tapi `enum` `Option` tersebut. 118 | 119 | Penggunaan `Option` type juga sama seperti `Result` type. Kita dapat menggunakan `match`, `unwrap`, `expect`, maupun operator `?` untuk me-handle `None`. 120 | 121 | ```rust 122 | fn count_word(sentence: String, word: &str) -> Option { 123 | let mut count = 0; 124 | let vec = sentence.split(" ").map(|s| s.to_string()).collect::>(); 125 | 126 | for i in vec { 127 | if i == word { 128 | count += 1; 129 | } 130 | } 131 | if count == 0 { 132 | None 133 | } 134 | else { 135 | Some(count) 136 | } 137 | } 138 | 139 | fn main() { 140 | let sentence = String::from("Aku sedang coding bahasa Rust dan bahasa Rust ini sangat keren"); 141 | let count = count_word(sentence, "Rust"); 142 | match count { 143 | None => println!("Tidak ditemukan kata tersebut di kalimat tersebut"), 144 | Some(value) => println!("Ditemukan {} kata tersebut di kalimat tersebut", value) 145 | }; 146 | } 147 | 148 | ``` 149 | 150 | Kode diatas merupakan sebuah contoh penggunaan `Option` type dimana `count_word` merupakan fungsi yang menghitung jumlah kata tertentu didalam sebuah kalimat. Bila kata ditemukan, maka jumlah kata akan dikembalikan dalam varian `Some` dan bila tidak, varian `None` akan dikembalikan dari fungsi `count_word`. Lalu pada fungsi `main`, tergantung pada kondisi apakah varian yang dikembalikan adalah `None` atau `Some`, output `Tidak ditemukan kata tersebut di kalimat tersebut` atau `Ditemukan kata tersebut di kalimat tersebut` akan dicetak. 151 | 152 | ### Unwrap Or 153 | 154 | `unwrap_or` merupakan sebuah method untuk melakukan `unwrap` terhadap `Option` type, namun tidak akan `panic` bila value yang di-unwrap ternyata `None`, melainkan akan lari ke value default yang didefinisikan. 155 | 156 | ```rust 157 | fn default_example(opt: Option<&str>) { 158 | println!("{}", opt.unwrap_or("Ini adalah value default")); 159 | } 160 | 161 | fn main() { 162 | default_example(Some("Test")); 163 | default_example(None); 164 | } 165 | ``` 166 | 167 | # Latihan 168 | 169 | TODO 170 | 171 | 172 | -------------------------------------------------------------------------------- /basics/06_result_dan_option_type/src/example.rs: -------------------------------------------------------------------------------- 1 | // untuk error handling di rust menggunakan result sebagai hasil dari suatu proses 2 | // result memiliki 2 kondisi utama yaitu Ok = hasil, dan Err = error 3 | 4 | fn get_ok() -> Result { 5 | // untuk mengembalikan hasil, dengan keyword Ok(value) 6 | Ok("ini result".to_string()) 7 | } 8 | 9 | fn get_err() -> Result { 10 | // untuk mengembalikan error, dengan keyword Err(value) 11 | Err("ini error".to_string()) 12 | } 13 | 14 | pub fn result_types() { 15 | // memanggil function untuk return result terlebih dahulu 16 | let result = get_ok(); 17 | // memastikan hasil yang di return Ok 18 | assert!(result.is_ok()); 19 | // simulasi Ok pattern matching untuk tipe data Result 20 | match result { 21 | Ok(result) => println!("Hasil Ok: {}", result), 22 | Err(e) => println!("Hasil Err: {}", e), 23 | } 24 | 25 | // sama seperti di atas, namun kali ini simulasi error 26 | let error = get_err(); 27 | // memastikan hasil yang di return Err 28 | assert!(error.is_err()); 29 | // simulasi Err pattern matching untuk tipe data Result 30 | match error { 31 | Ok(result) => println!("Hasil Ok: {}", result), 32 | Err(e) => println!("Hasil Err: {}", e), 33 | } 34 | 35 | // syntax function dengan pattern matching bisa di persingkat menjadi 36 | match get_ok() { 37 | Ok(result) => println!("Hasil dipersingkat Ok: {}", result), 38 | Err(e) => println!("Hasil Err: {}", e), 39 | } 40 | 41 | // syntax function dengan pattern matching bisa di persingkat menjadi 42 | match get_ok() { 43 | // disini kita bisa menambahkan wildcard (_) untuk mengabaikan hasil yang tidak digunakan 44 | // namun ini tidak disarankan, lebih baik di match secara explicit 45 | Ok(result) => println!("Hasil dengan wildcard Ok: {}", result), 46 | _ => (), 47 | } 48 | } 49 | 50 | /// bahasa pemrograman rust tidak memiliki nilai Null atau Nil seperti bahasa lainnya 51 | /// rust memiliki fitur option untuk mencegah null value yang berpotensi bug 52 | /// option memiliki 2 kondisi utama yaitu Some = ada value, dan None = tidak ada value 53 | 54 | fn get_some() -> Option { 55 | // Some di gunakan untuk mengembalikan Option dengan value 56 | Some("hasil dengan value".to_string()) 57 | } 58 | 59 | fn get_none() -> Option { 60 | // None di gunakan untuk mengembalikan Option tanpa value 61 | None 62 | } 63 | 64 | pub fn option_types() { 65 | // memanggil function untuk return option terlebih dahulu 66 | let option = get_some(); 67 | // memastikan return some value 68 | assert!(option.is_some()); 69 | // simulasi Some di pattern matching 70 | match option { 71 | Some(result) => println!("Hasil Some: {}", result), 72 | None => println!("Hasil None"), 73 | } 74 | 75 | // memanggil function untuk return option terlebih dahulu 76 | let option = get_none(); 77 | // memastikan return none value 78 | assert!(option.is_none()); 79 | // simulasi None di pattern matching 80 | match option { 81 | Some(result) => println!("Hasil Some: {}", result), 82 | None => println!("Hasil None"), 83 | } 84 | 85 | // persingkat pattern matching dengan function 86 | match get_some() { 87 | Some(result) => println!("Hasil persingkat Some: {}", result), 88 | None => println!("Hasil None"), 89 | } 90 | 91 | // kita bisa melakukan unwrap yaitu mengambil value di dalam type option ini 92 | // cara ini tidak disarankan, karena juga function return none akan menyebabkan panic 93 | let hasil_option = get_none(); 94 | assert!(hasil_option.is_none()); 95 | // kita bisa menggunakan default value, sangat disarankan sewaktu waktu hasil nya none, agar tidak panic 96 | let hasil = hasil_option.unwrap_or("ini hasil default".to_string()); 97 | // kode dibawah ini akan meyebabkan error, jangan unwrap begitu saja pada option! 98 | // let hasil = hasil_option.unwrap(); 99 | println!("Hasil unwrap dengan default: {}", hasil); 100 | 101 | let hasil_kondisi = get_some(); 102 | // if ini akan jalan jika di temui value dari option tersebut 103 | if let Some(result) = hasil_kondisi { 104 | // variable result yand di deklarasi sebagai some hanya bisa di akses di dalam if block saja 105 | println!("kondisi result true: {}", result); 106 | } 107 | 108 | // bisa di persingkat & simulasi jika value adalah none 109 | if let Some(result) = get_none() { 110 | // ini tidak akan jalan karna false! 111 | // variable result mengharapkan some value dari function, namun function mengembalikan none. 112 | println!("kondisi None & false: {}", result); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /basics/06_result_dan_option_type/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod example; 2 | 3 | #[cfg(test)] 4 | mod tests { 5 | use crate::example; 6 | 7 | #[test] 8 | fn example_test() { 9 | example::result_types(); 10 | example::option_types(); 11 | } 12 | 13 | #[test] 14 | fn start() {} 15 | } -------------------------------------------------------------------------------- /basics/07_conditional/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "conditional_bellshade" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /basics/07_conditional/README.md: -------------------------------------------------------------------------------- 1 | # Conditional 2 | 3 | Di chapter sebelumnya, kita telah mengetahui tentang `match`. Sekarang, kita akan masuk ke dalam `if` dan `else` statement. If-else statement pada Rust dapat digunakan dengan cara yang sama, dan juga berbeda dari bahasa lain. If-else dalam Rust dapat digunakan pada saat assignment variabel, seperti `match`. Penggunaan secara normal tidak berbeda jauh dengan bahasa lainnya. 4 | 5 | 6 | ```rust 7 | 8 | fn main() { 9 | 10 | let a = 10; 11 | 12 | if a > 10 { 13 | println!("A lebih besar dari 10"); 14 | } 15 | else if a == 10 { 16 | println!("A sama dengan 10"); 17 | } 18 | else { 19 | println!("A kurang dari 10"); 20 | } 21 | 22 | } 23 | 24 | ``` 25 | 26 | Dan penggunaannya dalam assignment, 27 | 28 | ```rust 29 | fn main() { 30 | 31 | let a = 10; 32 | 33 | let b = if a == 10 { 34 | 7 35 | } else { 36 | 2 37 | }; 38 | 39 | // OUTPUT: 7 40 | println!("{}", b); 41 | 42 | } 43 | ``` 44 | 45 | Namun, kita dapat menggunakan `if` dengan agak spesial, untuk pengganti pattern matching. 46 | 47 | ## If Let 48 | 49 | `if let` merupakan suatu fitur spesial yang dapat digunakan untuk `enum`. Statement ini digunakan untuk mengecek apakah sebuah `varian benar-benar merupakan varian tersebut. `if let` dapat digunakan untuk hal yang straightforward dan tidak memerlukan pattern matching. 50 | 51 | Sekarang perhatikan kode dibawah ini. 52 | 53 | ```rust 54 | 55 | let optional = Some(7); 56 | 57 | match optional { 58 | Some(i) => { 59 | println!("{}", i); 60 | 61 | }, 62 | _ => {}, 63 | }; 64 | ``` 65 | 66 | Kode diatas sangatlah _awkward_ dan terlalu panjang untuk operasi seperti itu. Kita bahkan tidak memakai `None` dan semua operasi adalah `void` atau tidak mengembalikan nilai apapun. Kita hanya ingin mencetak nilai yang di-wrap didalam varian `Some` pada variabel `optional`. disini kita hanya membutuhkan satu varian saja, yaitu `Some(i)` dimana ia sudah pasti bukan `None`. Nah, Rust memiliki cara yang lebih baik untuk hal seperti ini dengan menggunakan `if let`. 67 | 68 | 69 | ```rust 70 | let optional = Some(7); 71 | 72 | if let Some(i) = optional { 73 | println!("{}", i); 74 | } 75 | ``` 76 | 77 | Nah, dengan begini kode akan lebih pendek dari sebelumnya, dan juga tidak terlihat canggung lagi. Penggunaan `if let` adalah dengan menggunakan varian yang diinginkan, kemudian mengisinya dengan nilai bila varian tersebut memuat nilai, lalu menggunakan operator assignment, kita assign variabel yang ingin kita gunakan seperti `optional` pada diatas, kemudian dalam scope `if let` tersebut, kita dapat menggunakan nilai yang di-wrap didalam varian tersebut yaitu pada contoh diatas adalah `i`, sesuka hati kita. 78 | 79 | Kita juga dapat menggunakannya pada `enum` apapun termasuk yang kita definisikan sendiri. 80 | 81 | ```rust 82 | enum Test { 83 | A, 84 | B(i32), 85 | } 86 | 87 | fn main() { 88 | let a = Test::A; 89 | let b = Test::B(10); 90 | 91 | if let Test::A = a { 92 | println!("Varian A tanpa nilai") 93 | } 94 | 95 | if let Test::B(v) = b { 96 | println!("Varian B memiliki nilai: {}", v) 97 | } 98 | } 99 | ``` 100 | 101 | # LATIHAN 102 | 103 | TODO -------------------------------------------------------------------------------- /basics/07_conditional/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | #[test] 4 | fn it_works() { 5 | let result = 2 + 2; 6 | assert_eq!(result, 4); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /basics/08_loop/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "loop_bellshade" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /basics/08_loop/README.md: -------------------------------------------------------------------------------- 1 | # Loop 2 | 3 | Loop adalah urutan instruksi yang akan terus mengulang hingga suatu kondisi terpenuhi, atau tanpa batas. Loop pada bahasa Rust tidak jauh berbeda dengan bahasa lainnya. Rust juga memiliki `for loop` dan `while loop` seperti bahasa lainnya. 4 | 5 | ## For Loop 6 | 7 | Pada Rust, `for loop` terlihat seperti berikut: 8 | 9 | ```rust 10 | for i in iterable { 11 | println!("{}", &i); 12 | } 13 | ``` 14 | 15 | Dimana `iterable` merupakan tipe yang dapat diiterate. Contohnya, `Vec` dan `slice`. Kita juga dapat membuat `Range` dalam Rust, seperti pada python yaitu dengan cara berikut: 16 | 17 | ```rust 18 | for i in 0..100 { 19 | println!("{}", i); 20 | } 21 | ``` 22 | 23 | Kode diatas akan mencetak angka 0 sampai 99. 24 | 25 | ### Contoh pada Vector 26 | 27 | ```rust 28 | let vec = vec!["Satu", "Dua", "Tiga"]; 29 | 30 | for i in vec { 31 | println!("{}", i); 32 | } 33 | ``` 34 | 35 | ## While 36 | 37 | `while` loop pada Rust tidaklah berbeda dengan bahasa lainnya. Ia akan terus melakukan perulangan hingga kondisi yang tertulis tercapai. 38 | 39 | ```rust 40 | let mut i = 0; 41 | 42 | while i < 10 { 43 | i += 1; 44 | } 45 | ``` 46 | 47 | Pada kode diatas, while akan terus mengulang hingga `i` berjumlah 9. 48 | 49 | ## `Loop` 50 | 51 | `loop` disini digunakan sebagai perulangan yang akan terus mengulang, seperti `while true`. Dapat digunakan sebagai mainloop sebuah aplikasi. Penulisannya adalah sebagai berikut: 52 | 53 | ```rust 54 | use std::io; 55 | use std::io::Write; 56 | 57 | fn main() { 58 | let mut input = String::new(); 59 | loop { 60 | io::stdin().read_line(&mut input).unwrap(); 61 | println!("Yang anda masukkan adalah: {}", input); 62 | match io::stdout().flush() { 63 | Ok(_) => (), 64 | Err(e) => println!("{}", e), 65 | }; 66 | } 67 | } 68 | ``` 69 | 70 | `loop` akan terus mengulang kode diatas, dan akan terus meminta user input. 71 | 72 | # Latihan 73 | 74 | // TODO 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /basics/08_loop/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | #[test] 4 | fn it_works() { 5 | let result = 2 + 2; 6 | assert_eq!(result, 4); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /basics/09_struct_dan_implementation/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "struct_and_impl" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /basics/09_struct_dan_implementation/README.md: -------------------------------------------------------------------------------- 1 | # Struct dan Implementation 2 | 3 | Sebuah `struct` atau structure digunakan untuk merepresentasikan tipe data kompleks yang kita definisikan sendiri. Pada Rust, kita dapat membuat sebuah tipe baru dengan menggunakan `struct`. Kita dapat menambahkan method pada `struct` dengan menggunakan implementation block atau `impl`. 4 | 5 | ## Struct 6 | 7 | `struct` merupakan sebuah cara untuk mendefinisikan sebuah tipe data yang kompleks. Sebuah `struct` dapat memiliki beberapa field, yang masing-masing dapat memiliki tipe data yang berbeda. Sebagai contoh, kita akan membuat sebuah `struct` yang merepresentasikan seseorang. Kita akan menggunakan `struct` untuk menyimpan data-data yang berkaitan dengan orang tersebut. 8 | 9 | 10 | ```rust 11 | struct Person { 12 | name: String, 13 | age: u8, 14 | } 15 | ``` 16 | 17 | Lalu kita dapat membuat sebuah variabel baru bertipe `Person` dengan cara berikut: 18 | 19 | ```rust 20 | fn main() { 21 | let name = String::from("Fulan"); 22 | let age = 27; 23 | let person = Person { name, age }; 24 | } 25 | ``` 26 | 27 | ### Derive Trait 28 | 29 | Derive Trait adalah sebuah cara untuk mendapatkan implementasi dari beberapa trait secara otomatis. Sebagai contoh, kita dapat menggunakan `#[derive(Debug)]` untuk mendapatkan implementasi dari trait `Debug` secara otomatis. Contoh penggunaan derive trait adalah sebagai berikut: 30 | 31 | ```rust 32 | #[derive(Debug)] 33 | struct Person { 34 | name: String, 35 | age: u8, 36 | } 37 | ``` 38 | 39 | Sekarang, kita akan membahas beberapa derive trait berguna yang paling sering dipakai pada bahasa Rust. 40 | 41 | Sebagai catatan, untuk memakai derive trait, seluruh field dalam `struct` harus juga telah mengimplementasikan `trait` yang kita derive tersebut. Karena itu, kita tidak akan bisa menggunakan `trait` `Copy` tanpa `Clone` bila ada `String` didalam field `struct` kita. 42 | 43 | #### Debug 44 | 45 | `Debug` merupakan trait yang sangat berguna untuk melakukan debugging. Ia akan mencetak output sesuai dengan bentuk asli dari tipe kita. Pada macro `print!` atau `println!`, untuk dapat mencetak `Debug`, kita harus menggunakan formatting berikut: `:?`. 46 | 47 | Contoh penggunaan: 48 | 49 | ```rust 50 | #[derive(Debug)] 51 | struct Person { 52 | name: String, 53 | age: u8, 54 | } 55 | 56 | fn main() { 57 | let name = String::from("Fulan"); 58 | let age = 27; 59 | let person = Person { name, age }; 60 | println!("{:?}", person); 61 | } 62 | 63 | // OUTPUT: Person { name: "Fulan", age: 27 } 64 | ``` 65 | 66 | #### Clone 67 | 68 | Masih ingat dengan materi ownership sebelumnya? Dengan mengimplementasikan `Clone`, kita dapat melakukan cloning pada tipe kita dengan method `clone()` seperti yang sudah pernah dijelaskan di materi ownership sebelumnya. 69 | 70 | Contoh penggunaan: 71 | 72 | ```rust 73 | #[derive(Clone, Debug)] 74 | struct Person { 75 | name: String, 76 | age: u8, 77 | } 78 | 79 | fn main() { 80 | let name = String::from("Fulan"); 81 | let age = 27; 82 | let person = Person { name, age }; 83 | let person2 = person.clone(); 84 | println!("{:?}", person2); 85 | } 86 | ``` 87 | 88 | #### Copy 89 | 90 | Seperti `Clone`, `Copy` juga merupakan trait yang berguna untuk melakukan cloning. Namun, `Copy` memiliki beberapa syarat yang harus dipenuhi agar dapat digunakan. Syarat-syarat tersebut adalah sebagai berikut: 91 | 92 | * Semua field dalam `struct` harus mengimplementasikan `Copy` (Contohnya, kita tidak akan bisa menggunakan `Copy` untuk tipe yang memiliki field bertipe `String` didalamnya.). 93 | * `struct` tidak boleh memiliki implementasi dari `Drop` trait. 94 | 95 | Dengan `Copy`, tipe kita akan melakukan copy secara otomatis ketika kita melakukan assignment, seperti yang telah dijelaskan tentang `trait Copy` pada artikel ownership sebelumnya. Saat men-derive `Copy`, kita juga harus men-derive `Clone` 96 | 97 | Contoh penggunaan: 98 | 99 | ```rust 100 | #[derive(Copy, Clone, Debug)] 101 | struct Location { 102 | lat: f64, 103 | lon: f64, 104 | } 105 | 106 | fn main() { 107 | let loc = Location { lat: 10.55555, lon: 20.22222 }; 108 | let loc2 = loc; // Copy 109 | let loc3 = loc2; // Copy 110 | 111 | println!("{:?}", loc); 112 | println!("{:?}", loc2); 113 | println!("{:?}", loc3); 114 | } 115 | ``` 116 | 117 | ### Membuat format print untuk tipe kita 118 | 119 | Kita dapat membuat format tertentu dan bagaimana kita akan mencetak tipe kita sendiri dengan menggunakan trait `Display`. Untuk `Display`, kita tidak dapat menggunakan derive trait. `Display` akan mengimplementasikan `to_string()` secara otomatis juga pada tipe kita. 120 | 121 | Pneggunaan `Display` adalah sebagai berikut: 122 | 123 | ```rust 124 | use std::fmt; 125 | 126 | #[Clone, Debug] 127 | struct Person { 128 | name: String, 129 | age: u8, 130 | } 131 | 132 | impl fmt::Display for Person { 133 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 134 | write!(f, "Seseorang bernama {} dan berumur {}", self.name, self.age) 135 | } 136 | } 137 | 138 | fn main() { 139 | let name = String::from("Fulan"); 140 | let age = 27; 141 | let person = Person { name, age }; 142 | println!("{}", person); // Seseorang bernama Fulan dan berumur 27 143 | } 144 | ``` 145 | 146 | `println!` akan mencetak sesuai dengan yang telah kita definisikan pada implementasi `Display` kita, yang berada didalam macro `write!`. 147 | 148 | ### Membuat method untuk tipe kita 149 | 150 | Kita dapat membuat method untuk tipe kita sendiri dengan menggunakan `impl` block. Contoh penggunaan adalah sebagai berikut: 151 | 152 | ```rust 153 | #[derive(Clone, Debug)] 154 | struct Person { 155 | name: String, 156 | age: u8, 157 | } 158 | 159 | impl Person { 160 | 161 | fn new(name: String, age: u8) -> Person { 162 | Person { name, age } 163 | } 164 | 165 | fn say_hello(&self) { 166 | println!("Halo, nama saya {} dan berumur {}", self.name, self.age); 167 | } 168 | 169 | fn birthday(&mut self) { 170 | self.age += 1; 171 | } 172 | } 173 | 174 | fn main() { 175 | let name = String::from("Fulan"); 176 | let age = 27; 177 | let person = Person::new(name, age); 178 | person.say_hello(); 179 | 180 | println!("Saya ulang tahun!"); 181 | person.birthday(); 182 | 183 | println!("Umur saya sekarang adalah {}", person.age); 184 | } 185 | ``` 186 | 187 | Pada kode diatas, kita memiliki dua tipe method, yaitu method yang dapat langsung dipanggil dari tipenya, dan method yang harus dipanggil melalui variabel yang menyimpan tipe tersebut. Method yang dapat langsung dipanggil dari tipenya disebut dengan method `associated function`. Method yang harus dipanggil melalui variabel yang menyimpan tipe tersebut disebut dengan method `instance method`. Method `associated function` biasanya digunakan untuk membuat instance dari tipe tersebut, seperti yang terlihat pada method `new()` pada kode diatas. Ia dipanggil dengan menggunakan `::` setelah nama tipe, seperti yang terlihat pada `Person::new(name, age)`. Namun, method `instance method` biasanya digunakan untuk melakukan operasi pada instance dari tipe tersebut, seperti yang terlihat pada method `say_hello()` dan `birthday()` pada kode diatas. Ia dipanggil dengan menggunakan `.` setelah variabel yang menyimpan tipe tersebut, seperti yang terlihat pada `person.say_hello()` dan `person.birthday()`. 188 | 189 | 190 | // TODO: Tambahkan iterator, operator overloading, etc. 191 | 192 | # LATIHAN 193 | 194 | // TODO 195 | -------------------------------------------------------------------------------- /basics/09_struct_dan_implementation/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | #[test] 4 | fn it_works() { 5 | let result = 2 + 2; 6 | assert_eq!(result, 4); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /basics/10_trait/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "trait_bellshade" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /basics/10_trait/README.md: -------------------------------------------------------------------------------- 1 | # Trait 2 | 3 | Trait pada bahasa Rust merupakan sebuah cara kita mencapai polymorphism. Dengan trait, kita dapat mendefiniskan perlakuan yang sama, untuk tipe-tipe yang berbeda. Trait pada Rust mirip dengan interface pada bahasa pemrograman lainnya, namun lebih powerful. 4 | 5 | Pada `trait`, kita dapat mendefinisikan method-method yang harus diimplementasikan oleh sebuah tipe, kemudian memanggil method-method tersebut pada tipe tersebut. Sebagai contoh, kita dapat mendefinisikan sebuah trait `Animal` yang memiliki method `make_sound()`, kemudian kita dapat membuat sebuah struct `Cat` yang mengimplementasikan `Animal`, dan kita dapat memanggil method `make_sound()` pada `Cat`. 6 | 7 | Kita akan mencoba membuat struct `Cat` yang mengimplementasikan trait `Animal`. Pertama, kita akan mendefinisikan trait `Animal` dan struct `Cat` terlebih dahulu. 8 | 9 | ```rust 10 | trait Animal { 11 | fn make_sound(&self); 12 | } 13 | 14 | struct Cat { 15 | name: String, 16 | } 17 | ``` 18 | 19 | Lalu kita akan mengimplementasikan trait `Animal` untuk struct `Cat`. 20 | 21 | ```rust 22 | impl Animal for Cat { 23 | fn make_sound(&self) { 24 | println!("Meow!"); 25 | } 26 | } 27 | ``` 28 | 29 | Dan terakhir, kita akan membuat sebuah instance dari struct `Cat` dan memanggil method `make_sound()` pada instance tersebut. 30 | 31 | ```rust 32 | fn main() { 33 | let cat = Cat { name: String::from("Bacing") }; 34 | cat.make_sound(); 35 | } 36 | ``` 37 | 38 | Dengan begini, kita telah berhasil membuat sebuah trait dan mengimplementasikannya pada sebuah struct. Method `make_sound()` pada trait `Animal` dapat kita panggil pada instance dari struct `Cat`. 39 | 40 | Kalian pasti bertanya-tanya, kenapa kita tidak langsung saja membuat method `make_sound()` pada struct `Cat`? Nah, memang tidak berarti untuk membuat satu `trait` untuk satu tipe saja. Karena itu, kita akan membuat satu tipe lagi yang akan mengimplementasikan `Animal`. 41 | 42 | ```rust 43 | struct Dog { 44 | name: String, 45 | } 46 | 47 | impl Animal for Dog { 48 | fn make_sound(&self) { 49 | println!("Woof!"); 50 | } 51 | } 52 | ``` 53 | 54 | Diatas, kita telah membuat struct `Dog` dan mengimplementasikan `Animal` untuk `Dog`. Sekarang, kita akan membuat sebuah fungsi yang menerima sebuah tipe yang mengimplementasikan `Animal` sebagai parameter. 55 | 56 | ```rust 57 | fn make_animal_sound(animal: &dyn Animal) { 58 | animal.make_sound(); 59 | } 60 | ``` 61 | 62 | Kita akan membuat sebuah instance dari struct `Cat` dan `Dog`, kemudian kita akan memanggil fungsi `make_animal_sound()` pada kedua instance tersebut. 63 | 64 | ```rust 65 | fn main() { 66 | let cat = Cat { name: String::from("Bacing") }; 67 | let dog = Dog { name: String::from("Baguk") }; 68 | 69 | make_animal_sound(&cat); 70 | make_animal_sound(&dog); 71 | } 72 | ``` 73 | 74 | Dan hasilnya akan dikeluarkan sesuai dengan masing-masing implementasi dari method `make_sound`. Untuk misalnya fungsi yang melakukan hal yang sama untuk banyak tipe, kita tidak perlu membuat banyak fungsi untuk masing-masing tipe. Cukup satu dimana perbedaan yang muncul akan tergantung dengan implementasi didalam tipe itu sendiri. 75 | 76 | Keyword `dyn` diatas harus digunakan bila kita memakai `trait` sebagai parameter. Namun, ada cara yang lebih baik selain menggunakan keyword `dyn` terutama bila kita menginginkan untuk memakai lebih dari satu trait, yaitu dengan generics. 77 | 78 | ```rust 79 | fn make_animal_sound(animal: T) { 80 | animal.make_sound(); 81 | } 82 | ``` 83 | 84 | Yang mana akan kita bahas dengan lebih lanjut di bagian advanced. 85 | 86 | # LATIHAN 87 | 88 | // TODO -------------------------------------------------------------------------------- /basics/10_trait/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | #[test] 4 | fn it_works() { 5 | let result = 2 + 2; 6 | assert_eq!(result, 4); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /extras/smart_pointers/01_box.md: -------------------------------------------------------------------------------- 1 | # Box Smart Pointer 2 | 3 | Sebuah pointer, sebuah konsep umum untuk sebuah variabel yang menyimpan alamat memory. Alamat memory ini menunjuk - pointing pada sebuah data. Smart Pointer, atau pointer pintar namun, adalah struktur data yang tidak hanya berperilaku seperti sebuah pointer, namun juga memiliki kapabilitas lain. Konsep smart pointer ini berawal dari C++. 4 | 5 | Di artikel ini, kita akan membahas penggunaan Box, sebuah smart pointer yang sangat umum digunakan di Rust. Box digunakan untuk menunjuk pada data di heap. Box memiliki kapabilitas untuk mengalokasikan data di heap dan menghapusnya ketika sudah tidak digunakan. 6 | 7 | Penggunaan Box adalah sebagai berikut 8 | 9 | ```rust 10 | fn main() { 11 | let boxed_value = Box::new(10); 12 | println!("{}", boxed_value); 13 | } 14 | ``` 15 | 16 | Dalam kode diatas, kita mengalokasikan 10 yang merupakan sebuah integer - tipe primitif pada heap, yang seharusnya ada pada stack. Box tidak mengimplementasikan `Copy` karena ia bukan tipe primitif sehingga, bila ingin menggunakannya berulang kali, kita harus menggunakan borrow (&), atau clone untuk variabel box. 17 | 18 | ```rust 19 | fn main() { 20 | let boxed_value = Box::new(10); 21 | let clone_box = boxed_value.clone(); 22 | println!("{}", clone_box); 23 | } 24 | ``` 25 | 26 | ### Penggunaan Box Lebih Mendalam 27 | 28 | Sekarang, lihat `enum` berikut 29 | 30 | ```rust 31 | #[derive(Debug)] 32 | enum List { 33 | Cons(T, List), 34 | Nil, 35 | } 36 | ``` 37 | 38 | `enum` di atas merupakan struktur data `Cons List` yang berasal dari bahasa Lisp. Struktur data diatas akan terus memuat data di dalam varian `Cons` secara rekursif hingga ia menemukan `Nil`. Setelah menemukan `Nil`, ia akan berhenti disana tanpa data apapun lagi. Bila kalian sudah membaca bab `enum`, kalian pasti mengerti cara kerjanya. 39 | 40 | Namun, perlu ditegaskan bahwa `enum` di atas tidak akan bekerja. Mengapa? 41 | 42 | Rust harus mengetahui berapa besar ruang yang sebuah tipe ambil pada saat compile time. Sedangkan pada `enum` diatas, ia bersifat rekursif yang dalam teori, dia dapat berulang selamanya - tidak terbatas. Ia dapat terus memuat varian `Cons` yang memuat tipe `List` yang berupa varian `Cons` juga dan terus begitu. Rust tidak mengetahui berapa besar si `enum` `List` pada saat compile time. 43 | 44 | Mari kita coba mengimplementasikan contoh Cons List diatas lalu kita compile. 45 | 46 | ```rust 47 | #[derive(Debug)] 48 | enum List { 49 | Cons(T, List), 50 | Nil, 51 | } 52 | 53 | use List::*; 54 | 55 | fn main() { 56 | let l = Cons(42, Cons(69, Cons(613, Nil))); 57 | 58 | println!("{:?}", l); 59 | } 60 | ``` 61 | 62 | Bila kita mengcompile kode diatas, kita akan mendapatkan error berikut: 63 | 64 | ``` 65 | 2 | enum List { 66 | | ^^^^^^^^^^^^ recursive type has infinite size 67 | 3 | Cons(T, List), 68 | | ------- recursive without indirection 69 | | 70 | help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to make `List` representable 71 | | 72 | 3 | Cons(T, Box>), 73 | ``` 74 | 75 | Error diatas menunjukkan bahwa kita memiliki tipe rekursif dengan ukuran tidak terbatas - rekursif tanpa indirection. Error diatas juga membantu kita dengan memberitahu bahwa kita harus me-wrap `List` dalam `Box`. Indirection disini berarti daripada kita menyimpan nilai dari `List` yang rekursif secara langsung, kita harus menyimpan sebuah pointer, yang mengarah kepada nilai dari `List` - yaitu `Box` tersebut. 76 | 77 | Sebelum kita membahas mengapa Box menyelesaikan masalah diatas, mari kita bahas bagaimana compiler Rust menghitung enum yang non-rekursif. 78 | 79 | ```rust 80 | enum Enum { 81 | A, 82 | B(i32, i32), 83 | C(f64, i64, String), 84 | } 85 | ``` 86 | 87 | Disini kita memiliki sebuah `enum` dengan 3 varian dimana dua varian memiliki nilai didalamnya. Cara Rust menghitung besar `enum` diatas adalah dengan mengecek setiap varian dan tipe nilai yang dimiliki varian dan mencari varian mana yang membutuhkan ruang paling banyak - atau varian dengan ukuran terbesar. Karena kita hanya bisa menggunakan satu varian dalam satu waktu, maka besar varian yang paling besar akan menjadi besar dari `Enum` itu sendiri. 88 | 89 | Namun, untuk `Cons` `List` kita, saat Rust bertemu dengan tipe `List` dalam varian `Cons`, ia akan kembali lagi pada `List`, dan berulang terus seperti itu sehingga tidak ada cara untuk mengetahui berapa besar si varian `Cons` kita dan Rust tidak akan tahu juga berapa besar `enum` `List` kita. 90 | 91 | Sekarang, seperti yang Rust compiler sarankan, kita akan me-wrap `List` kita di dalam `Box`. Mari kita lakukan. 92 | 93 | ```rust 94 | #[derive(Debug)] 95 | enum List { 96 | Cons(T, Box>), 97 | Nil, 98 | } 99 | 100 | use List::*; 101 | 102 | fn main() { 103 | 104 | // Untuk me-wrap nilai dalam Box, gunakan Box::new(nilai) 105 | let l = Cons(42, Box::new(Cons(69, Box::new(Cons(613, Box::new(Nil)))))); 106 | 107 | println!("{:?}", l); 108 | } 109 | ``` 110 | 111 | Dan kode kita akan tercompile: 112 | 113 | Cons(42, Cons(69, Cons(613, Nil))) 114 | 115 | Lalu bagaimanakah `Box` menyelesaikan masalah ini? Pertama-tama, `Box` adalah sebuah pointer. Ukuran dari sebuah pointer itu tetap. Ukuran pointer tidak berdasarkan besar atau jumlah data yang dia tunjuk. `Box` menunjuk pada nilai `List` kita selanjutnya yang berada pada memori heap, bukan pada varian `Cons` sehingga ini akan seperti menaruh sesuatu bersebelahan dengan sesuatu yang lain, bukan menaruh sesuatu didalam sesuatu yang lain dan `Box` menunjuk pada sesuatu yang bersebelahan tersebut yang dalam hal ini adalah nilai dari `List` yang di-wrap dalam `Box` pada varian `Cons`. 116 | 117 | Kesimpulan: Pada Rust, `usize` itu pointer-sized sehingga ukuran dari `Cons` adalah ukuran dari tipe yang kita berikan pada genericnya, dan `usize` karena kita menyimpan pointer. -------------------------------------------------------------------------------- /extras/smart_pointers/02_deref_trait.md: -------------------------------------------------------------------------------- 1 | # `Deref` Trait 2 | 3 | Sebuah smart pointer adalah sebuah tipe yang mengimplementasikan trait Deref dan trait Drop. Di artikel kali ini, kita akan membahas tentang trait Deref yang membuat kita dapat memperlakukan sebuah pointer seperti sebuah reference biasa. Lalu apa maksud dari memperlakukan sebuah pointer seperti sebuah reference biasa? 4 | 5 | Sebelum itu, mari kita membahas lebih lanjut tentang Dereferencing. 6 | 7 | ## Dereferencing 8 | 9 | Dereferencing adalah sebuah cara untuk mengakses nilai dari sebuah lokasi memori yang ditunjuk oleh sebuah pointer. Pada Rust, seperti dalam bahasa seperti C++, kita menggunakan operator * untuk dereferencing. 10 | 11 | Sekarang, mari kita lihat kode berikut 12 | 13 | ```rust 14 | fn main() { 15 | let a = 10; 16 | let b = &a; 17 | 18 | assert_eq!(10, a); 19 | assert_eq!(10, b); 20 | } 21 | ``` 22 | 23 | Kita menggunakan macro assert_eq! pada kode diatas untuk mengecek apakah sebuah nilai setara (equal) dengan nilai yang lainnya. Namun, hal yang akan terjadi saat kita compile kode diatas adalah compile error yakni sebagai berikut 24 | 25 | ``` 26 | error[E0277]: can't compare `{integer}` with `&{integer}` 27 | --> deref.rs:7:2 28 | | 29 | 7 | assert_eq!(10, b); 30 | | ^^^^^^^^^^^^^^^^^ no implementation for `{integer} == &{integer}` 31 | | 32 | = help: the trait `PartialEq<&{integer}>` is not implemented for `{integer}` 33 | ``` 34 | 35 | Error diatas menyatakan kalau kita tidak bisa membandingkan sebuah integer dengan sebuah reference kepada sebuah integer. Mereka adalah tipe yang berbeda sehingga kita harus menggunakan dereference operator. 36 | 37 | Dereference operator menggunakan tanda asterisk (*) 38 | 39 | ```rust 40 | fn main() { 41 | let a = 10; 42 | let b = &a; 43 | 44 | assert_eq!(10, a); 45 | assert_eq!(10, *b); 46 | } 47 | ``` 48 | 49 | Pada Rust, sebuah reference (&) sebenarnya juga merupakan pointer. Jadi, variabel b diatas adalah sebuah pointer yang menyimpan alamat memory a dan menunjuk kepada dimana valuenya, 10 disimpan. 50 | 51 | Pada assertion pertama, kita membandingkan 10 dan a, yang hasilnya adalah benar. Pada assertion kedua, kita membandingkan 10 dan b yang sudah kita dereference sehingga b disana merupakan value yang ia tunjuk, yaitu 10. Kode akan berjalan dengan baik. 52 | 53 | ## Apa itu Trait `Deref`? 54 | 55 | Kita dapat memperlakukan sebuah smart pointer seperti sebuah reference biasa. 56 | 57 | Untuk contoh yang lebih lanjut, kita akan mengganti kode diatas dan menggunakan sebuah smart pointer daripada sebuah reference. Kita akan menggunakan `Box`. 58 | 59 | ```rust 60 | fn main() { 61 | let a = 10; 62 | let b = Box::new(a); 63 | 64 | assert_eq!(10, a); 65 | assert_eq!(10, *b); 66 | } 67 | ``` 68 | 69 | Seperti reference, `Box` juga menunjuk kepada nilai yang disimpan di suatu tempat di memori, yang dalam hal ini adalah `10`. Perbedaannya disini adalah `b` menunjuk pada sebuah copy dari `10` karena value tipe primitif akan di-copy, bukan di-move ownershipnya. 70 | 71 | `Box` merupakan sebuah smart pointer yang mengimplementasikan trait `Deref`. Inilah yang dimaksud dengan memperlakukan sebuah pointer seperti sebuah reference biasa. Trait `Deref` memperbolehkan dereference operator bekerja pada `Box` sama seperti ia bekerja pada reference biasa. 72 | 73 | Untuk mengerti bagaimana itu bekerja, kita akan mendefinisikan sebuah smart pointer kita sendiri yang akan mengimplementasikan `Deref`. 74 | 75 | ### Mendefinisikan smart pointer kita sendiri 76 | 77 | Kita akan mendefiniskan sebuah smart pointer yang serupa dengan `Box`. Hanya saja, disini kita tidak akan menyimpan nilai pada heap. Disini kita akan berfokus pada dereference operator, bukan dimana lokasi data disimpan. 78 | 79 | ```rust 80 | struct Kotak(T); 81 | 82 | impl Kotak { 83 | fn new(x: T) -> Self { 84 | Self(x) 85 | } 86 | } 87 | ``` 88 | 89 | Sekarang, kita ganti `Box` di fungsi main dengan `Kotak` kita. 90 | 91 | ```rust 92 | fn main() { 93 | let a = 10; 94 | let b = Kotak::new(a); 95 | 96 | assert_eq!(10, a); 97 | assert_eq!(10, *b); 98 | } 99 | ``` 100 | 101 | Namun pada kode diatas, kita akan mendapatkan error dimana kita tidak bisa melakukan dereference pada tipe `Kotak` kita. Sekarang, mari kita implementasikan trait `Deref`. 102 | 103 | #### Mengimplementasikan `Deref` pada smart pointer kita 104 | 105 | Pertama-tama. panggil trait `Deref` di baris paling atas kode. 106 | 107 | ```rust 108 | use std::ops::Deref; 109 | ``` 110 | 111 | Lalu kita implementasikan pada `Kotak`. 112 | 113 | ```rust 114 | impl Deref for Kotak { 115 | type Target = T; 116 | 117 | fn deref(&self) -> &Self::Target { 118 | &self.0 119 | } 120 | } 121 | ``` 122 | 123 | Kalian tidak perlu terlalu mengkhawatirkan `type Target = T` untuk sekarang. Itu adalah sebuah associated type yang akan kita bahas di lain waktu. Kalian juga bisa mengganti return type method `deref` menjadi hanya `&T`. 124 | 125 | Trait `Deref` mengharuskan kita untuk mengimplementasikan satu method bernama `deref` yang menerima `&self`, dan mengembalikan sebuah reference kepada inner data atau data didalam `struct` kita. Ingat, kita disini memakai `tuple struct` yang menggunakan index 0, 1, dan seterusnya untuk mengambil inner data. 126 | 127 | Sekarang, kode kita akan terlihat seperti ini 128 | 129 | ```rust 130 | use std::ops::Deref; 131 | 132 | struct Kotak(T); 133 | 134 | impl Kotak { 135 | fn new(x: T) -> Self { 136 | Self(x) 137 | } 138 | } 139 | 140 | impl Deref for Kotak { 141 | type Target = T; 142 | 143 | fn deref(&self) -> &Self::Target { 144 | &self.0 145 | } 146 | } 147 | 148 | fn main() { 149 | let a = 10; 150 | let b = Kotak::new(a); 151 | 152 | assert_eq!(10, a); 153 | assert_eq!(10, *b); 154 | } 155 | ``` 156 | 157 | Dan assertion kedua kita akan berhasil. Kita akan dapat melakukan dereference pada tipe `Kotak` kita. Kode akan dapat kita compile. 158 | 159 | Tanpa trait `Deref`, compiler hanya mengetahui cara dereference reference saja. Trait `Deref` membuat compiler Rust untuk memanggil method `deref` untuk semua tipe yang mengimplementasikannya - untuk mendapatkan sebuah reference kepada sebuah nilai (Self::Target atau &T kita diatas), yang si compiler tahu bagaimana cara dereferencenya. 160 | 161 | Saat kita menggunakan operator dereference kepada sebuah nilai yang telah mengimplementasikan trait `Deref`, assertion kedua kita di atas contohnya, sebenarnya Rust memanggil kode seperti berikut: 162 | 163 | ```rust 164 | assert_eq!(10, *(b.deref())); 165 | ``` 166 | 167 | Rust akan memanggil method `deref` terlebih dahulu untuk mendapatkan reference kepada nilai kita, yang pada kasus diatas adalah `10`, lalu melakukan dereferencing dengan operator dereference sehingga kira-kira hal yang terjadi adalah berikut: 168 | 169 | `Kotak(10) -> deref() terpanggil -> &10 -> dereference operator digunakan -> 10` 170 | 171 | Karena Rust melakukan hal itu secara otomatis, kita tidak perlu memikirkan perlu atau tidaknya memanggil method `deref` secara eksplisit sehingga kita bisa memperlakukan reference biasa, dan sebuah tipe yang mengimplementasikan trait `Deref` dengan sama. 172 | 173 | Lalu kenapa method `deref` mengembalikan reference kepada suatu nilai bukan nilainya itu sendiri? 174 | 175 | Tentunya itu berhubungan dengan ownership pada Rust. Kalau `deref` mengembalikan nilainya secara langsung, maka ownership dari nilai tersebut akan di-move keluar dari tipe kita, yang dalam kasus ini, smart pointer kita Kotak. Dan di banyak kasus saat kita menggunakan operator dereference, kita tidak mau itu terjadi. 176 | 177 | ## Deref Coercion 178 | 179 | Kita telah melihat bagaimana trait `Deref` bekerja. Sekarang, kita akan melihat bagaimana trait `Deref` bekerja dengan `Deref Coercion`. 180 | 181 | Deref Coercion adalah sebuah fitur sangat praktis yang Rust akan secara otomatis gunakan pada dan hanya pada tipe yang mengimplementasikan trait `Deref` ketika tipe tersebut dijadikan argumen untuk fungsi atau method. Deref Coercion akan mengubah sebuah reference dari satu tipe kepada sebuah reference dari tipe yang berbeda. 182 | 183 | Untuk lebih jelasnya, mari kita lanjutkan kode diatas dengan menambahkan sebuah prosedur untuk mencetak `&str`. 184 | 185 | ```rust 186 | fn main() { 187 | let a = 10; 188 | let b = Kotak::new(a); 189 | 190 | assert_eq!(10, a); 191 | assert_eq!(10, *b); 192 | } 193 | 194 | fn prosedur(a: &str) { 195 | println!("A adalah: {}", a); 196 | } 197 | ``` 198 | 199 | Sekarang, kita akan membuat sebuah variabel baru yang menggunakan smart pointer `Kotak` kita dan sebuah `String` untuk nilai didalam `Kotak` kita, lalu kita panggil prosedur kita dengan variabel tersebut sebagai argumen. 200 | 201 | ```rust 202 | fn main() { 203 | let a = 10; 204 | let b = Kotak::new(a); 205 | 206 | assert_eq!(10, a); 207 | assert_eq!(10, *b); 208 | 209 | let c = Kotak::new(String::from("Hai")); 210 | prosedur(&c); 211 | } 212 | 213 | fn prosedur(a: &str) { 214 | println!("A adalah: {}", a); 215 | } 216 | ``` 217 | 218 | Seperti yang kalian lihat, prosedur `prosedur` menerima `&str` sebagai argumen. Diatas, kita memberikannya sebuah reference kepada `Kotak` yang memiliki `String` didalamnya. Namun, kode diatas tidak akan error! Kode diatas akan berjalan dengan sempurna. 219 | 220 | Apa yang terjadi disini? 221 | 222 | Inilah yang terjadi: 223 | 224 | Saat kita memakai operator reference di argumen prosedur pada variabel `c`, method `deref` akan terpanggil dan kita akan mendapatkan sebuah reference kepada `String`, nilai yang kita wrap dalam `Kotak`. 225 | 226 | ```rust 227 | &Kotak -> &String 228 | ``` 229 | Lalu, karena `String` juga mengimplementasikan trait `Deref`, bila kita menggunakan operator dereference, `String` akan mengembalikan sebuah `&str` sehingga hal yang akan terjadi berikutnya adalah: 230 | 231 | ```rust 232 | &Kotak -> &String -> &str 233 | ``` 234 | 235 | Rust melakukannya dengan otomatis. Tanpa Deref Coercion, bila ingin melakukan hal seperti diatas, kita harus menuliskannya seperti ini: 236 | 237 | ```rust 238 | prosedur(&(*c)[..]); 239 | ``` 240 | 241 | Disana kita melakukan dereference pada `c` sehingga kita mendapat sebuah `String`, kemudian `&` dan `[..]` (`slice` yang berisi operator `RangeFull`) akan membuat sebuah `&str` dari si `String` yang setara dengan panjang penuh (full range) si `String`. Sangat merepotkan bukan? Kode akan lebih sulit ditulis dan lebih sulit dibaca. Terima kasih Deref Coercion! 242 | 243 | Untuk mutable reference, kita harus menggunakan trait `DerefMut`. 244 | 245 | Rust melakukan Deref Coercion bila ia bertemu tipe dan implementasi trait dalam tiga kasus: 246 | 247 | - Dari `&T` ke `&U` ketika `T: Deref` 248 | - Dari `&mut T` ke `&mut U` ketika `T: DerefMut` 249 | - Dari `&mut T` ke `&U` ketika `T: Deref` 250 | -------------------------------------------------------------------------------- /extras/smart_pointers/03_drop_trait.md: -------------------------------------------------------------------------------- 1 | # Drop Trait 2 | 3 | Seperti yang telah kita ketahui, smart pointer adalah sebuah tipe yang mengimplementasikan trait `Deref` dan `Drop`. Kita sudah membahas tentang `Deref` di artikel sebelumnya. Kali ini, kita akan membahas tentang `Drop`. 4 | 5 | Trait `Drop` dapat diimplementasikan pada tipe apapun, dan hampir akan selalu digunakan ketika kita mengimplementasikan sebuah smart pointer. Trait `Drop` adalah sebuah trait yang membuat kita dapat mengatur atau mengkustomisasi apa yang akan terjadi bila sebuah nilai keluar dari scope-nya (out of scope). Mari kita ambil `Box` sebagai contoh. Implementasi kustom tentang apa yang akan terjadi ketika sebuah nilai keluar dari scope-nya pada `Box` adalah, ia akan mendealokasikan nilai yang ia tunjuk pada heap. 6 | 7 | Sekarang, mari kita gunakan constraint trait `Debug` pada smart pointer `Kotak` kita yang telah kita buat di materi `Deref` dan semua implementasinya agar kita dapat menampilkan value `T` pada smart pointer `Kotak` kita. 8 | 9 | ```rust 10 | use std::ops::Deref; 11 | 12 | struct Kotak(T); 13 | 14 | impl Kotak 15 | where T: std::fmt::Debug { 16 | fn new(x: T) -> Self { 17 | Self(x) 18 | } 19 | } 20 | 21 | impl Deref for Kotak 22 | where T: std::fmt::Debug { 23 | type Target = T; 24 | 25 | fn deref(&self) -> &Self::Target { 26 | &self.0 27 | } 28 | } 29 | ``` 30 | 31 | Lalu kita implementasikan `Drop` pada `Kotak` kita. 32 | 33 | ```rust 34 | impl Drop for Kotak 35 | where T: std::fmt::Debug { 36 | fn drop(&mut self) { 37 | println!("Dropping Kotak yang memiliki data {:?}!", self.0); 38 | } 39 | } 40 | ``` 41 | 42 | Kita hanya akan menggunakan macro `println!` untuk sekarang, karena sekarang kita hanya akan berfokus pada bagaimana trait `Drop` bekerja, dan tidak pada implementasi kustom aslinya seperti mendealokasikan nilai di heap. Sekarang, mari kita lihat fungsi `main` kita. 43 | 44 | ```rust 45 | fn main() { 46 | let x = Kotak::new(20); 47 | { 48 | let y = Kotak::new("Halo"); 49 | } 50 | } 51 | ``` 52 | 53 | Bila kita compile lalu kita jalankan kode kita, maka kita akan menerima output seperti ini: 54 | 55 | ``` 56 | Dropping Kotak yang memiliki data "Halo"! 57 | Dropping Kotak yang memiliki data 20! 58 | ``` 59 | 60 | Lihat, begitu variabel `Kotak` kita menyentuh akhir dari scope, maka apa yang ada di dalam method drop kita akan terpanggil. Diatas, variabel `x` akan di-drop ketika ia menyentuh akhir dari scope `main` sehingga fungsi `y` akan lebih dahulu di-drop. Karena itulah output `Dropping Kotak yang memiliki data "Halo"!` keluar lebih dahulu. Selalu ingat bahwa trait `Drop` ini digunakan untuk mengkustomisasi apa yang akan dilakukan ketika data kita di-drop, bukan kita harus mengimplementasikan trait `Drop` terlebih dahulu baru data kita dapat di-drop. Rust akan secara otomatis men-drop nilai yang sudah mencapai akhir scope. 61 | 62 | ## Early Drop 63 | 64 | Ada kalanya kita ingin men-drop nilai kita lebih dahulu sebelum mencapai akhir scope, seperti saat kita menggunakan lock atau `Mutex`. Rust melarang kita untuk memanggil method `drop` secara langsung dari sebuah instance yang mengimplementasikannya. Kita harus menggunakan fungsi `drop` dari `std::mem::drop`. Kita tidak perlu menggunakan use untuk memanggil fungsi tersebut. Seperti `Vec`, fungsi itu sudah tersedia di dalam prelude. 65 | 66 | ```rust 67 | fn main() { 68 | let x = Kotak::new(20); 69 | drop(x); 70 | { 71 | let y = Kotak::new("Halo"); 72 | } 73 | } 74 | ``` 75 | 76 | Dan output yang akan dikeluarkan adalah: 77 | 78 | ``` 79 | Dropping Kotak yang memiliki data 20! 80 | Dropping Kotak yang memiliki data "Halo"! 81 | ``` 82 | 83 | Variabel `x` akan di-drop terlebih dahulu. -------------------------------------------------------------------------------- /extras/smart_pointers/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bellshade_smartptrs" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /extras/smart_pointers/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub fn add(left: usize, right: usize) -> usize { 2 | left + right 3 | } 4 | 5 | #[cfg(test)] 6 | mod tests { 7 | use super::*; 8 | 9 | #[test] 10 | fn it_works() { 11 | let result = add(2, 2); 12 | assert_eq!(result, 4); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /git_hooks/pre-commit: -------------------------------------------------------------------------------- 1 | cargo fmt 2 | cargo test 3 | -------------------------------------------------------------------------------- /intermediate/01_generics/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "generics_bellshade" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /intermediate/01_generics/README.md: -------------------------------------------------------------------------------- 1 | # Generics 2 | 3 | Generics atau Generic Type merupakan tipe yang dapat digunakan untuk berbagai tipe data. Dengan menggunakan generics, kita dapat membuat sebuah fungsi yang dapat digunakan untuk berbagai tipe data, tanpa harus membuat fungsi yang sama untuk berbagai tipe data - yang membuat kode kita lebih reusable. Compiler akan melakukan *monomorphization* pada fungsi yang menggunakan generics, sehingga fungsi yang menggunakan generics akan menjadi fungsi yang spesifik untuk tipe data tertentu. 4 | 5 | ## Membuat Generic Struct 6 | 7 | Kita akan membuat sebuah struct `Point` yang memiliki dua field, `x` dan `y`. Kita akan membuat `Point` menjadi generic, sehingga kita dapat membuat `Point` dengan tipe data yang berbeda-beda. 8 | 9 | ```rust 10 | #[derive(Debug)] 11 | struct Point { 12 | x: T, 13 | y: T, 14 | } 15 | ``` 16 | 17 | `T` diatas merupakan tipe placeholder atau generic parameter, yang berfungsi seperti parameter sebuah fungsi yaitu mewakili data yang akan dimasukkan. Namun, generic parameter berfungsi untuk mewakili tipe yang akan dimasukkan, bukan sebuah nilai. 18 | 19 | Sekarang, kita akan membuat instance dari `Point` dengan tipe data yang berbeda-beda. 20 | 21 | ```rust 22 | fn main() { 23 | let integer = Point { x: 5, y: 10 }; 24 | let float = Point { x: 1.0, y: 4.0 }; 25 | } 26 | ``` 27 | 28 | Kemudian kita gunakan `println!` untuk mencetak `Point` yang kita buat. 29 | 30 | ```rust 31 | fn main() { 32 | let integer = Point { x: 5, y: 10 }; 33 | let float = Point { x: 1.0, y: 4.0 }; 34 | 35 | println!("integer point = {:?}", integer); 36 | println!("float point = {:?}", float); 37 | } 38 | ``` 39 | 40 | Output: 41 | 42 | ```text 43 | integer point = Point { x: 5, y: 10 } 44 | float point = Point { x: 1.0, y: 4.0 } 45 | ``` 46 | 47 | Generics akan secara otomatis menentukan tipe data yang kita masukkan seperti integer pada variabel `integer` dan float pada variabel `float`. 48 | 49 | Kita juga dapat membuat instance `Point` seperti ini untuk mendefinisikan tipe yang ingin kita pakai secara lebih eksplisit: 50 | 51 | ```rust 52 | let integer: Point = Point { x: 5, y: 10 }; 53 | let float: Point = Point { x: 1.0, y: 4.0 }; 54 | ``` 55 | 56 | Selalu ingat bahwa `T` hanya bisa dimasukkan satu tipe yang sama. Jika kita ingin membuat `Point` yang memiliki dua field dengan tipe data yang berbeda, kita bisa membuat struct seperti ini: 57 | 58 | ```rust 59 | #[derive(Debug)] 60 | struct Point { 61 | x: T, 62 | y: U, 63 | } 64 | ``` 65 | 66 | Dan kemudian membuat instancenya seperti ini: 67 | 68 | ```rust 69 | let both_integer = Point { x: 5, y: 10 }; 70 | let both_float = Point { x: 1.0, y: 4.0 }; 71 | let integer_and_float = Point { x: 5, y: 4.0 }; 72 | ``` 73 | 74 | Kita juga dapat menggunakan generics pada `enum` 75 | 76 | ```rust 77 | enum Enum { 78 | One(T), 79 | Two, 80 | } 81 | ``` 82 | 83 | 84 | ## Trait Constraint dan Generic Function 85 | 86 | Selain pada `struct` dan `enum`, kita juga dapat menggunakan menggunakan generics pada fungsi. Kita akan membuat sebuah fungsi untuk membandingkan parameter `a` dan `b` dimana `a` dan `b` adalah sebuah generics `T`. 87 | 88 | ```rust 89 | fn compare(a: T, b: T) -> T { 90 | if a > b { 91 | return a; 92 | } 93 | b 94 | } 95 | 96 | fn main() { 97 | let a = 5; 98 | let b = 10; 99 | let comp = compare(a, b); 100 | println!("{}", comp); 101 | } 102 | ``` 103 | Pada kode diatas, yang akan terjadi adalah sebuah error seperti berikut: 104 | 105 | ``` 106 | error[E0369]: binary operation `>` cannot be applied to type `T` 107 | --> test.rs:22:10 108 | | 109 | 22 | if a > b { 110 | | - ^ - T 111 | | | 112 | | T 113 | | 114 | help: consider restricting type parameter `T` 115 | | 116 | 21 | fn compare(a: T, b: T) -> T { 117 | | ++++++++++++++++++++++ 118 | ``` 119 | 120 | Error diatas menunjukkan kalau operator `>` tidak bisa digunakan untuk tipe `T`. Hal ini terjadi karena compiler tidak dapat mengetahui apakah `T` memiliki implementasi operator `>` atau tidak. Untuk mengatasi ini, kita dapat menggunakan trait constraint untuk membatasi tipe `T` yang dapat digunakan pada fungsi `compare` seperti yang ditunjukan pada `help: consider restricting type parameter `T``. 121 | 122 | ```rust 123 | use std::cmp::PartialOrd; 124 | 125 | fn compare(a: T, b: T) -> T { 126 | if a > b { 127 | return a; 128 | } 129 | b 130 | } 131 | 132 | fn main() { 133 | let a = 5; 134 | let b = 10; 135 | let comp = compare(a, b); 136 | println!("{}", comp); 137 | } 138 | ``` 139 | 140 | Dan outputnya adalah `10` dari variabel `b`. 141 | 142 | Trait constraint ini berfungsi untuk membatasi suatu tipe generics. Logikanya adalah, hanya tipe yang sudah mengimplementasikan constraint tersebutlah yang dapat masuk ke dalam fungsi tersebut. Dengan demikian, kita dapat memastikan bahwa fungsi tersebut akan berjalan dengan baik. Pada kode diatas, `T` hanya bisa dimasukkan oleh tipe yang sudah mengimplementasikan trait `PartialOrd`. Variabel `a` dan `b` yang berupa `i32` telah mengimplementasikan `PartialOrd` sehingga fungsi bekerja dengan sangat baik. Bila kita memasukkan tipe yang tidak mengimplementasikan `PartialOrd` seperti `String`, maka fungsi tersebut akan error. 143 | 144 | ## Generics Struct dan Implementation 145 | 146 | Disini kita akan membuat sebuah trait `Animal` dan beberapa struct yang mengimplementasikan `Animal`. Kemudian kita akan membuat satu struct lagi bernama `Pet` dimana `Pet` akan menerima `T` dimana `T` merupakan `Animal`, lalu membuat method di dalam `Pet` yang akan memakai `T` untuk melakukan sesuatu. 147 | 148 | ```rust 149 | trait Animal { 150 | fn name(&self) -> String; 151 | } 152 | 153 | struct Cat { 154 | name: String, 155 | } 156 | 157 | struct Dog { 158 | name: String, 159 | } 160 | 161 | struct Cow { 162 | name: String, 163 | } 164 | 165 | impl Animal for Cat { 166 | fn name(&self) -> String { 167 | self.name.clone() 168 | } 169 | } 170 | 171 | impl Animal for Dog { 172 | fn name(&self) -> String { 173 | self.name.clone() 174 | } 175 | } 176 | 177 | impl Animal for Cow { 178 | fn name(&self) -> String { 179 | self.name.clone() 180 | } 181 | } 182 | 183 | struct Pet { 184 | animal: T, 185 | } 186 | 187 | impl Pet { 188 | fn new(animal: T) -> Pet { 189 | Pet { animal } 190 | } 191 | 192 | fn name(&self) -> String { 193 | self.animal.name() 194 | } 195 | 196 | fn pat(&self) { 197 | println!("Mengelus {}", self.name()); 198 | } 199 | } 200 | 201 | fn main() { 202 | let cat = Cat { name: String::from("Kitty") }; 203 | let dog = Dog { name: String::from("Doggy") }; 204 | let cow = Cow { name: String::from("Cowy") }; 205 | 206 | let cat_pet = Pet::new(cat); 207 | let dog_pet = Pet::new(dog); 208 | let cow_pet = Pet::new(cow); 209 | 210 | println!("Cat name: {}", cat_pet.name()); 211 | println!("Dog name: {}", dog_pet.name()); 212 | println!("Cow name: {}", cow_pet.name()); 213 | 214 | cat_pet.pat(); 215 | dog_pet.pat(); 216 | cow_pet.pat(); 217 | } 218 | ``` 219 | 220 | Pada kode di atas, terlihat bahwa kita dapat memasukkan tipe apapun yang mengimplementasikan `Animal`. Karena Rust memiliki trait-based generics, jadi manipulasi yang terjadi kepada tipe yang mengimplementasikan trait tersebut ada pada methodnya, yang didefinisikan didalam traitnya seperti method `name()` yang ada pada trait `Animal` yang kita pakai berulang kali di dalam implementasi `Pet`. Karena itulah ada _constraint_ atau batasan yaitu trait, dimana method yang dipakai harus berada dalam trait yang kita pakai sebagai constraint. Dengan begitu, kita dapat melakukan operasi yang bertujuan sama, namun implementasinya berbeda pada setiap tipe. Dengan demikian, kita dapat membuat sebuah fungsi yang dapat digunakan untuk tipe yang berbeda-beda, namun memiliki implementasi yang sama yang pada kode di atas, merupakan method dari `Pet`. 221 | 222 | # LATIHAN 223 | 224 | Buatlah implementasi trait `MakhlukHidup` untuk `struct` `Orang` dan `Kucing` sesuai dengan yang terlihat dalam macro `assert_eq!` pada `src/lib.rs` dan kemudian jalankan `cargo test`! 225 | 226 | 227 | -------------------------------------------------------------------------------- /intermediate/01_generics/src/lib.rs: -------------------------------------------------------------------------------- 1 | trait MakhlukHidup { 2 | fn makan(&self) -> String; 3 | } 4 | 5 | struct Orang { 6 | nama: String, 7 | } 8 | 9 | struct Kucing { 10 | nama: String, 11 | } 12 | 13 | fn makan_makhluk(makhluk: T) -> String { 14 | makhluk.makan() 15 | } 16 | 17 | impl MakhlukHidup for Orang { 18 | } 19 | 20 | impl MakhlukHidup for Kucing { 21 | } 22 | 23 | #[cfg(test)] 24 | mod tests { 25 | use super::*; 26 | 27 | #[test] 28 | fn start() { 29 | let orang = Orang { 30 | nama: String::from("Reimu"), 31 | }; 32 | let kucing = Kucing { 33 | nama: String::from("Chen"), 34 | }; 35 | 36 | let orang_makan = makan_makhluk(orang); 37 | let kucing_makan = makan_makhluk(kucing); 38 | 39 | assert_eq!(orang_makan, "Reimu makan nasi"); 40 | assert_eq!(kucing_makan, "Chen makan whiskas"); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /intermediate/02_lifetime/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bellshade_lifetime" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /intermediate/02_lifetime/README.md: -------------------------------------------------------------------------------- 1 | # Lifetime 2 | 3 | Dalam bahasa Rust, setiap variabel atau objek memiliki lifetime yang terkait dengannya, yaitu berapa lama variabel atau objek tersebut diperlukan dan digunakan dalam program. Rust memastikan bahwa memori yang digunakan oleh variabel atau objek hanya disediakan selama periode waktu yang diperlukan, dan tidak lebih lama dari itu. 4 | 5 | Lifetime biasanya ditentukan oleh tempat variabel atau objek dideklarasikan, serta hubungannya dengan variabel atau objek lain dalam program. Rust menggunakan aturan-aturan tertentu untuk menentukan lifetime secara otomatis, sehingga programmer tidak perlu secara manual menentukan lifetime setiap variabel atau objek. 6 | 7 | Sebagai contoh, Rust akan menentukan lifetime dari variabel `x` sebagai berikut: 8 | 9 | ```rust 10 | fn main() { 11 | let x = 5; 12 | } 13 | ``` 14 | 15 | Karena `x` dideklarasikan di dalam fungsi `main`, maka `x` akan memiliki lifetime yang sama dengan fungsi `main`. Ketika fungsi `main` selesai dieksekusi, maka `x` akan dihapus dari memori. 16 | 17 | Sekarang, lihat kode dibawah ini. 18 | 19 | ```rust 20 | fn main() { 21 | let x = 5; 22 | 23 | { 24 | let y = &x; 25 | println!("{}", y); 26 | } 27 | } 28 | ``` 29 | 30 | Karena `y` dideklarasikan di dalam sebuah *scope* baru, maka `y` akan memiliki lifetime yang sama dengan *scope* tersebut dan lifetimenya akan berakhir ketika ia mencapai akhir scope. Di sini, `y` adalah *reference* yang menunjuk pada variabel `x`. Karena `y` hanya memiliki lifetime yang sama dengan *scope*-nya, maka Rust memastikan bahwa referensi ini tidak akan mencoba untuk mengakses memori yang tidak valid. Lifetime disini menunjukkan bahwa `y` akan tetap valid selama `x` masih ada di memori, dan ia belum keluar dari *scope* miliknya. 31 | 32 | ## Explicit Annotation 33 | 34 | Ada kalanya Rust tidak dapat menentukan lifetime secara implisit, seperti saat kita menggunakan *reference* pada tipe data kompleks seperti `struct` atau `enum`, Rust tidak dapat menentukan lifetime secara otomatis. Dalam kasus ini, programmer harus secara manual menentukan lifetime dari variabel atau objek tersebut. Hal ini disebut dengan *explicit annotation*. 35 | 36 | Lihat contoh dibawah ini. 37 | 38 | ```rust 39 | fn main() { 40 | let x = 5; 41 | 42 | let result = get_value(&x); 43 | 44 | println!("{}", result); 45 | } 46 | 47 | fn get_value(&x: i32) -> &i32 { 48 | x 49 | } 50 | 51 | ``` 52 | 53 | Kode diatas akan berjalan dengan baik karena Rust dapat menentukan lifetime secara otomatis. Hal ini disebut dengan *lifetime elision*. 54 | 55 | Namun, jika kita mengubah kode tersebut menjadi seperti dibawah ini, maka Rust akan mengeluarkan error. 56 | 57 | ```rust 58 | fn main() { 59 | let x = 5; 60 | let y = 10; 61 | 62 | let result = get_bigger(&x, &y); 63 | 64 | println!("{}", result); 65 | } 66 | 67 | fn get_bigger(a: &i32, b: &i32) -> &i32 { 68 | if a > b { 69 | a 70 | } else { 71 | b 72 | } 73 | } 74 | ``` 75 | 76 | ``` 77 | | 78 | 10 | fn get_bigger(a: &i32, b: &i32) -> &i32 { 79 | | ---- ---- ^ expected named lifetime parameter 80 | | 81 | = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `a` or `b` 82 | help: consider introducing a named lifetime parameter 83 | | 84 | 10 | fn get_bigger<'a>(a: &'a i32, b: &'a i32) -> &'a i32 { 85 | | 86 | ``` 87 | 88 | Hal ini disebabkan oleh Rust yang tidak dapat menentukan *reference* mana yang akan dikembalikan oleh fungsi `get_bigger`. Bisa jadi `a` dan `b` memiliki lifetime yang berbeda dan Rust tidak mengetahui harus mengembalikan lifetime yang mana untuk *reference* yang dikembalikan. Karena itu, kita harus secara manual menentukan lifetime parameter dari variabel `a` dan `b` dengan menambahkan lifetime specifier di antara reference dan tipe data disana. 89 | 90 | Biasanya, nama lifetime parameter dimulai dari `'a`, `'b`, dan seterusnya. Namun, kita juga dapat memberikan nama lain untuk lifetime parameter, seperti `'x`, `'y`, dan seterusnya. 91 | 92 | ```rust 93 | fn main() { 94 | let x = 5; 95 | let y = 10; 96 | 97 | let result = get_bigger(&x, &y); 98 | 99 | println!("{}", result); 100 | } 101 | 102 | fn get_bigger<'a>(a: &'a i32, b: &'a i32) -> &'a i32 { 103 | if a > b { 104 | a 105 | } else { 106 | b 107 | } 108 | } 109 | ``` 110 | 111 | Dengan begini, Rust mengetahui bahwa fungsi `get_bigger` akan mengembalikan *reference* yang memiliki lifetime yang sama dengan variabel `a` dan `b`. 112 | 113 | ## Lifetime pada Struct 114 | 115 | Bila sebuah `struct` memiliki field yang merupakan *reference*, Rust tidak dapat menentukan lifetime secara otomatis. Kita harus secara manual menentukan lifetime parameter dari `struct` tersebut. 116 | 117 | ```rust 118 | struct Magician<'a> { 119 | name: &'a str, 120 | power: &'a str, 121 | } 122 | 123 | fn main() { 124 | let name = "Marisa Kirisame"; 125 | let power = "Heat Magic"; 126 | 127 | let magician = Magician { 128 | name, 129 | power, 130 | }; 131 | 132 | println!("{} has {} power", magician.name, magician.power); 133 | } 134 | ``` 135 | 136 | Dan kemudian, pada *implementation block* kita dapat menuliskannya seperti ini. 137 | 138 | ```rust 139 | impl<'a> Magician<'a> { 140 | fn new(name: &'a str, power: &'a str) -> Self { 141 | Self { 142 | name, 143 | power, 144 | } 145 | } 146 | 147 | fn introduce(&self) { 148 | println!("{} has {} power", self.name, self.power); 149 | } 150 | } 151 | ``` 152 | 153 | ## Static Lifetime 154 | 155 | Lifetime `static` adalah lifetime yang paling panjang, yaitu selama program berjalan. Lifetime `static` biasanya digunakan untuk variabel atau objek yang memiliki nilai yang tetap selama program berjalan, seperti konstanta. 156 | 157 | ```rust 158 | static PI: f64 = 3.14159265359; 159 | 160 | fn main() { 161 | println!("{}", PI); 162 | } 163 | ``` 164 | 165 | `PI` yang dideklarasikan sebagai `static` akan memiliki lifetime `static` yang sama dengan program. Ketika program selesai dieksekusi, maka `PI` akan dihapus dari memori. 166 | 167 | Penggunaan lifetime annotation `'static` juga dapat digunakan. Biasanya dalam kasus pengembalian *value* yang bertipe `&str` dari fungsi. 168 | 169 | ```rust 170 | fn main() { 171 | let result = get_name(); 172 | 173 | println!("{}", result); 174 | } 175 | 176 | fn get_name() -> &'static str { 177 | "Marisa Kirisame" 178 | } 179 | ``` 180 | 181 | Di Rust, `str` selalu menjadi sebuah *reference* karena ia merepresentasikan string slice yang menunjuk ke sebuah urutan byte-byte yang UTF-8 di dalam memori. String slice merupakan tampilan atau representasi dari sebuah string, sehingga ia meminjam memori yang mendasari yang memuat byte-byte dari string tersebut. Oleh karena itu, str selalu menjadi sebuah reference, yaitu &str. 182 | 183 | Karena lifetime sebuah *reference* hanya berlaku pada *scope* tempat ia dibuat, `&str` yang merupakan sebuah *reference* akan memaksa kita untuk menggunakan lifetime `static` dimana ia akan memiliki lifetime yang sama dengan program dalam kasus di atas. -------------------------------------------------------------------------------- /intermediate/02_lifetime/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub fn add(left: usize, right: usize) -> usize { 2 | left + right 3 | } 4 | 5 | #[cfg(test)] 6 | mod tests { 7 | use super::*; 8 | 9 | #[test] 10 | fn it_works() { 11 | let result = add(2, 2); 12 | assert_eq!(result, 4); 13 | } 14 | } 15 | --------------------------------------------------------------------------------