A tour of the standard library
Sekarang setelah Anda mengetahu banyak hal tentang Rust, Anda akan dapat memahami sebagian besar hal yang ada di dalam standard library. Code yang berada di dalamnya tidaklah lagi terlihat begitu menakutkan. Mari kita lihat beberapa bagian di dalamnya yang belum kita pelajari. Tour ini akan membahas sebagain besar standard library yang mana tidak perlu lagi Anda install di Rust. Kita akan meninjau ulang banyak hal yang telah kita ketahui sehingga kita dapat mempelajarinya dengan pemahaman yang lebih baik.
Arrays
Satu hal yang perlu dicatat tentang array adalah bahwa mereka tidak mengimplementasikan Iterator.
. Yang artinya, jika kita memiliki array, Anda tidak bisa menggunakan for
. Tapi Anda bisa menggunakan method seperti .iter()
pada array. Atau Anda bisa menggunakan &
untuk mendapatkan slicenya. Sebenarnya compiler akan memberitahukannya jika Anda mencoba untuk menggunakan for
:
fn main() { // ⚠️ let my_cities = ["Beirut", "Tel Aviv", "Nicosia"]; for city in my_cities { println!("{}", city); } }
Pesan errornya adalah:
error[E0277]: `[&str; 3]` is not an iterator
--> src\main.rs:5:17
|
| ^^^^^^^^^ borrow the array with `&` or call `.iter()` on it to iterate over it
Jadi, kita coba keduanya. Keduanya memberikan hasil yang sama.
fn main() { let my_cities = ["Beirut", "Tel Aviv", "Nicosia"]; for city in &my_cities { println!("{}", city); } for city in my_cities.iter() { println!("{}", city); } }
Hasil cetaknya adalah:
Beirut
Tel Aviv
Nicosia
Beirut
Tel Aviv
Nicosia
Jika Anda ingin mendapatkan variabel dari array, Anda bisa meletakkan nama variabelnya di dalam []
untuk melakukan destructure. Ini sama seperti menggunakan sebuah tuple di dalam statement match
atau mengambil variabel dari sebuah struct.
fn main() { let my_cities = ["Beirut", "Tel Aviv", "Nicosia"]; let [city1, city2, city3] = my_cities; println!("{}", city1); }
Hasil cetaknya adalah Beirut
.
char
Anda bisa menggunakan method .escape_unicode()
untuk mendapatkan angka Unicode dari sebuah char
:
fn main() { let korean_word = "청춘예찬"; for character in korean_word.chars() { print!("{} ", character.escape_unicode()); } }
Hasil cetaknya adalah \u{ccad} \u{cd98} \u{c608} \u{cc2c}
.
Anda bisa mendapatkan char dari u8
menggunakan trait From
, namun untuk u32
Anda perlu menggunakan TryFrom
karena bisa saja ia gagal. Ada banyak angka di u32
daripada seluruh karakter yang ada di Unicode. Kita bisa melihat ini dengan contoh yang sederhana.
use std::convert::TryFrom; // Anda perlu menggunakan TryFrom use rand::prelude::*; // kita akan menggunakan angka random juga fn main() { let some_character = char::from(99); // Untuk hal ini sangatlah mudah dilakukan - tidak perlu menggunakan TryFrom println!("{}", some_character); let mut random_generator = rand::thread_rng(); // Ia akan mencoba 40,000 kali untuk membuat sebuah char dari u32. // Range loopnya adalah dari 0 (std::u32::MIN) sampai ke angka terbesar di u32 (std::u32::MAX). Jika ia tidak bekerja, kita akan memberinya '-'. for _ in 0..40_000 { let bigger_character = char::try_from(random_generator.gen_range(std::u32::MIN..std::u32::MAX)).unwrap_or('-'); print!("{}", bigger_character) } }
Hampir setiap saat ia akan men-generate -
. Ini adalah bagian dari output yang akan Anda lihat:
------------------------------------------------------------------------𤒰---------------------
-----------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------
-------------------------------------------------------------춗--------------------------------
-----------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------
----------------------------------------------------------------
Jadi, adalah hal yang baik jika Anda perlu menggunakan TryFrom
.
Juga, sebagaimana pada rilis di akhir Agustus 2020, Anda sekarang bisa mendapatkan String
dari char
. (String
mengimplementasikan From<char>
) Cukup tuliskan String::from()
dan letakkan char
di dalamnya.
Integers
Ada banyak method matematis untuk type ini, ditambah dengan method-method lainnya. Berikut adalah beberapa method yang paling berguna.
.checked_add()
, .checked_sub()
, .checked_mul()
, .checked_div()
. Ini merupakan method yang baik jika Anda berpikir bahwa Anda mungkin mendapatkan angka yang tidak cocok dengan sebuah type. Method-method tersebut akan mengembalikan Option
sehingga Anda bisa dengan aman memeriksa apakah operasi matematika yang Anda lakukan bekerja tanpa membuat programnya menjadi panic.
fn main() { let some_number = 200_u8; let other_number = 200_u8; println!("{:?}", some_number.checked_add(other_number)); println!("{:?}", some_number.checked_add(1)); }
Hasilnya adalah:
None
Some(201)
Anda pasti menyadari pada laman tentang integer, banyak tertulis rhs
. Ini berarti "right hand side", yang artinya adalah sisi sebelah kanan di saat Anda melakukan beberapa operasi matematika. Contohnya, di 5 + 6
, 5
ada di sebelah kiri dan 6
ada di sebelah kanan. Nah, itulah rhs
. Ini bukanlah sebuah keyword, namun Anda akan sering melihatnya, sehingga ini adalah hal yang penting untuk Anda ketahui.
Sementara kita membahas topik ini, mari mempelajari tentang bagaimana mengimplementasikan Add
. Setelah Anda mengimplementasikan Add
, Anda bisa menggunakan +
pada type yang kita buat. Anda perlu untuk mengimplementasikan Add
sendiri karena "add" bisa berarti banyak hal. Berikut adalah contoh yang ada pada laman standard library:
#![allow(unused)] fn main() { use std::ops::Add; // pertama-tama, kita import Add #[derive(Debug, Copy, Clone, PartialEq)] // PartialEq mungkin adalah bagian yang paling penting pada bagian ini. Anda ingin bisa membandingkan angka struct Point { x: i32, y: i32, } impl Add for Point { type Output = Self; // Ingat, ini disebut sebagai "associated type": a "type yang berjalan bersamaan". // Pada kasus ini, ia hanyalah sebuah Point fn add(self, other: Self) -> Self { Self { x: self.x + other.x, y: self.y + other.y, } } } }
Sekarang mari implementasikan Add
untuk type yang kita buat. Mari kita bayangkan bahwa kita ingin menambahkan dua negara bersamaan sehingga kita bisa membandingkan ekonominya. Berikut adalah codenya:
use std::fmt; use std::ops::Add; #[derive(Clone)] struct Country { name: String, population: u32, gdp: u32, // ini adalah ukuran ekonominya } impl Country { fn new(name: &str, population: u32, gdp: u32) -> Self { Self { name: name.to_string(), population, gdp, } } } impl Add for Country { type Output = Self; fn add(self, other: Self) -> Self { Self { name: format!("{} and {}", self.name, other.name), // Kita akan menambahkan namanya bersama-sama, population: self.population + other.population, // dan populasinya, gdp: self.gdp + other.gdp, // dan juga GDPnya } } } impl fmt::Display for Country { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "In {} are {} people and a GDP of ${}", // Kemudian kita bisa print semuanya hanya dengan menggunakan {} self.name, self.population, self.gdp ) } } fn main() { let nauru = Country::new("Nauru", 10_670, 160_000_000); let vanuatu = Country::new("Vanuatu", 307_815, 820_000_000); let micronesia = Country::new("Micronesia", 104_468, 367_000_000); // Kita bisa memberikan Country sebuah &str sebagai ganti String untuk namanya. Tapi kita harus menuliskan lifetimenya dimana-mana // dan itu terlalu berlebihan untuk contoh yang sederhana. Lebih baik cukup clone saja di saat kita memanggil println!. println!("{}", nauru.clone()); println!("{}", nauru.clone() + vanuatu.clone()); println!("{}", nauru + vanuatu + micronesia); }
Hasilnya adalah:
In Nauru are 10670 people and a GDP of $160000000
In Nauru and Vanuatu are 318485 people and a GDP of $980000000
In Nauru and Vanuatu and Micronesia are 422953 people and a GDP of $1347000000
Nantinya di code ini kita bisa mengubah .fmt()
untuk menampilkan angka yang lebih mudah dibaca.
Tiga trait lainnya adalah Sub
, Mul
, dan Div
. Dan pada dasarnya, ketiganya sama pengimplementasiannya. Untuk +=
, -=
, *=
dan /=
, cukup tambahkan Assign
: AddAssign
, SubAssign
, MulAssign
, dan DivAssign
. Anda bisa melihat daftarnya disini, karena masih ada banyak lagi. Contohnya, %
disebut dengan Rem
, -
disebut dengan Neg
, dll.
Floats
f32
dan f64
memiliki method yang sangat banyak yang bisa Anda gunakan di saat Anda melakukan operasi matematis. Kita tidak akan mempelajari semuanya, namun ini adalah beberapa method yang mungkin nantinya Anda gunakan. Mereka adalah: .floor()
, .ceil()
, .round()
, dan .trunc()
. Semua method tersebut mengembalikan f32
atau f64
yang mirip seperti integer, namun dengan angka 0
setelah titik. Inilah yang dilakukan oleh method-method tersebut:
.floor()
: memberikan Anda integer terkecil..ceil()
: memberikan Anda integer terbesar..round()
: memberikan Anda integer terbesar jika angkanya bernilai 0.5 atau lebih, atau angka yang sama jika ia kurang dari 0.5. Ini disebut sebagai "rounding" (pembulatan) karena ia memberikan angka "yang dibulatkan" (angka yang memiliki bentuk yang pendek, sederhana)..trunc()
: memotong angka setelah titik. Truncate artinya "memotong".
Ini adalah function sederhana untuk mencetak hasil dari method-method tersebut.
fn four_operations(input: f64) { println!( "For the number {}: floor: {} ceiling: {} rounded: {} truncated: {}\n", input, input.floor(), input.ceil(), input.round(), input.trunc() ); } fn main() { four_operations(9.1); four_operations(100.7); four_operations(-1.1); four_operations(-19.9); }
Hasil cetaknya adalah:
For the number 9.1:
floor: 9
ceiling: 10
rounded: 9 // because less than 9.5
truncated: 9
For the number 100.7:
floor: 100
ceiling: 101
rounded: 101 // because more than 100.5
truncated: 100
For the number -1.1:
floor: -2
ceiling: -1
rounded: -1
truncated: -1
For the number -19.9:
floor: -20
ceiling: -19
rounded: -20
truncated: -19
f32
dan f64
memiliki method bernama .max()
dan .min()
yang memberikan Anda nilai tertinggi atau nilai terendah dari dua buah angka. (Untuk type yang lain, Anda bisa menggunakan std::cmp::max
dan std::cmp::min
.) Berikut ini adalah cara menggunakan method-method tersebut dengan method .fold()
untuk mendapatkan nilai tertinggi atau terendah. Anda bisa melihat lagi bahwa method .fold()
tidak hanya digunakan untuk menambahkan angka.
fn main() { let my_vec = vec![8.0_f64, 7.6, 9.4, 10.0, 22.0, 77.345, 10.22, 3.2, -7.77, -10.0]; let maximum = my_vec.iter().fold(f64::MIN, |current_number, next_number| current_number.max(*next_number)); // Catatan: mulai dengan angka terkecil di f64. let minimum = my_vec.iter().fold(f64::MAX, |current_number, next_number| current_number.min(*next_number)); // Dan pada bagian ini, mulai dengan angka terbesar di f64 println!("{}, {}", maximum, minimum); }
bool
Di Rust, Anda bisa mengubah bool
menjadi sebuah integer jika Anda menginginkannya, karena hal itu aman untuk dilakukan. Namun Anda tidak bisa melakukan hal tersebut sebaliknya. Seperti yang Anda lihat, true
diubah menjadi 1 dan false
diubah menjadi 0.
fn main() { let true_false = (true, false); println!("{} {}", true_false.0 as u8, true_false.1 as i32); }
Hasil cetaknya adalah 1 0
. Atau Anda bisa menggunakan .into()
jika Anda memberitahukan typenya ke compiler:
fn main() { let true_false: (i128, u16) = (true.into(), false.into()); println!("{} {}", true_false.0, true_false.1); }
Hasil cetaknya sama seperti yang di atas.
Dengan rilir Rust 1.50 (rilis pada Februari 2021), sekarang ada method bernama then()
, yang mana mengubah bool
menjadi Option
. Dengan method then()
, Anda menuliskan sebuah closure, dan closurenya dipanggil jika itemnya bernilai true
. Juga, apapun yang di return dari closure tersebut akan dimasukkan ke dalam Option
. Ini adalah contoh sederhananya:
fn main() { let (tru, fals) = (true.then(|| 8), false.then(|| 8)); println!("{:?}, {:?}", tru, fals); }
Hasil cetaknya adalah Some(8), None
.
Dan yang di bawah ini merupakan contohnya yang agak rumit:
fn main() { let bool_vec = vec![true, false, true, false, false]; let option_vec = bool_vec .iter() .map(|item| { item.then(|| { // masukkan ke dalam map sehingga kita bisa pass item tersebut println!("Got a {}!", item); "It's true, you know" // Ini akan masuk ke dalam Some jika ia true // sebaliknya, ia akan menghasilkan None }) }) .collect::<Vec<_>>(); println!("Now we have: {:?}", option_vec); // Ia akan mencetak None juga. Mari kita gunakan filter_map ke vector tersebut dan kumpulkan hasilnya di Vec yang baru. let filtered_vec = option_vec.into_iter().filter_map(|c| c).collect::<Vec<_>>(); println!("And without the Nones: {:?}", filtered_vec); }
Dan ini adalah hasil cetaknya:
Got a true!
Got a true!
Now we have: [Some("It\'s true, you know"), None, Some("It\'s true, you know"), None, None]
And without the Nones: ["It\'s true, you know", "It\'s true, you know"]
Vec
Vec memiliki banyak method yang sama sekali kita pelajari untuk sekarang ini. Kita mulai dari .sort()
. .sort()
menggunakan &mut self
untuk menyortir vector.
fn main() { let mut my_vec = vec![100, 90, 80, 0, 0, 0, 0, 0]; my_vec.sort(); println!("{:?}", my_vec); }
Hasil cetaknya adalah [0, 0, 0, 0, 0, 80, 90, 100]
. Namun yang lebih menarik adalah ada cara lain untuk menyortir yaitu .sort_unstable()
, dan ia biasanya lebih cepat. Ia bisa menjadi lebih cepat karena ia tidak peduli tentang urutan angkanya jika angkanya sama. Pada .sort()
, Anda tahu bahwa bagian akhir vector 0, 0, 0, 0, 0
akan berada pada urutan yang sama setelah melakukan .sort()
. Namun .sort_unstable()
mungkin memndahkan nol terakhir ke index 0, kemudian nol terakhir yang ke-3 di pindah ke index 2, dst.
.dedup()
artinya "de-duplicate". Ia akan menghapus item yang sama dalam sebuah vector, namun hanya jika mereka bersebelahana. Code di bawah ini tidak hanya mencetak "sun", "moon"
:
fn main() { let mut my_vec = vec!["sun", "sun", "moon", "moon", "sun", "moon", "moon"]; my_vec.dedup(); println!("{:?}", my_vec); }
Ia hanya akan menghapus "sun" yang berada di sebelah "sun", kemudian menghapus "moon" yang berada di sebelah "moon", dan kemudian lagi dengan "moon" yang bersebelahan dengan "moon". Maka hasilnya adalah: ["sun", "moon", "sun", "moon"]
.
Jika Anda ingin menghapus setiap duplikat, maka lakukan .sort()
terlebih dahulu:
fn main() { let mut my_vec = vec!["sun", "sun", "moon", "moon", "sun", "moon", "moon"]; my_vec.sort(); my_vec.dedup(); println!("{:?}", my_vec); }
Maka hasilnya adalah: ["moon", "sun"]
.
String
Anda akan mengingat bahwa String
adalah type yang mirip dengan Vec
. Ia mirip seperti Vec
Anda bisa melakukan banyak hal dengan method yang sama. Contohnya, Anda bisa menggunakan String::with_capacity()
. Anda mungkin akan memerlukannya jika Anda selalu melakukan push pada char
menggunakan .push()
atau melakukan push terhadap &str
dengan menggunakan .push_str()
. Ini adalah sebuah contoh dari String
yang terlalu banyak melakukan alokasi.
fn main() { let mut push_string = String::new(); let mut capacity_counter = 0; // kapasitasnya dimulai dari 0 for _ in 0..100_000 { // lakukan sebanyak 100,000 kali if push_string.capacity() != capacity_counter { // Pertama, periksa apakah kapasitasnya sekarang telah berbeda println!("{}", push_string.capacity()); // Jika ya, cetak kapasitasnya capacity_counter = push_string.capacity(); // kemudian perbarui counternya } push_string.push_str("I'm getting pushed into the string!"); // dan push &str ini setiap loopnya berulang } }
Hasilnya adalah:
35
70
140
280
560
1120
2240
4480
8960
17920
35840
71680
143360
286720
573440
1146880
2293760
4587520
Kita telah melakukan realokasi (menyalin secara keseluruhan) sebanyak 18 kali. Namun sekarang kita tahu berapa kapasitas akhirnya yang terpakai. Sehingga kita bisa langsung memberikan batas kapasitasnya, dan kita tidak perlu lagi untuk melakukan realokasi: cukup memerlukan satu buah String
dengan kapasitas yang muat untuk menyimpan semuanya sampai akhir.
fn main() { let mut push_string = String::with_capacity(4587520); // Kita mengetahui kapasitasnya secara tepat. Beberapa ukuran besar yang berbeda juga bisa dipakai let mut capacity_counter = 0; for _ in 0..100_000 { if push_string.capacity() != capacity_counter { println!("{}", push_string.capacity()); capacity_counter = push_string.capacity(); } push_string.push_str("I'm getting pushed into the string!"); } }
Dan ia mencetak hanya sekali, yaitu 4587520
. Sempurna! Kita tidak perlu melakukan alokasi lagi.
Tentu saja, panjang sebenarnya pasti lebih kecil dari ini. Jika Anda mencoba 100,001 kali, 101,000 kali, dst., ia akan tetap mencetak 4587520
. Ini dikarenakan setiap kali kapasitasnya bertambah, ia bertambah dua kali lipat dari yang sebelumnya. Kita bisa mengecilkannya menggunakan method .shrink_to_fit()
(sama juga dengan Vec
). String
yang kita miliki sangatlah besar dan kita tidak ingin menambahkan apa-apa lagi ke String
tersebut, sehingga kita bisa membuatnya menjadi lebih kecil dari yang sebelumnya. Tapi lakukan hal ini jika Anda sudah merasa yakin. Inilah alasannya:
fn main() { let mut push_string = String::with_capacity(4587520); let mut capacity_counter = 0; for _ in 0..100_000 { if push_string.capacity() != capacity_counter { println!("{}", push_string.capacity()); capacity_counter = push_string.capacity(); } push_string.push_str("I'm getting pushed into the string!"); } push_string.shrink_to_fit(); println!("{}", push_string.capacity()); push_string.push('a'); println!("{}", push_string.capacity()); push_string.shrink_to_fit(); println!("{}", push_string.capacity()); }
Hasilnya adalah:
4587520
3500000
7000000
3500001
Jadi pertama kita memiliki ukuran kapasitas 4587520
, namun kita tidak menggunakan semuanya. Kita gunakan method .shrink_to_fit()
dan mendapatkan kapasitasnya mengecil menjadi 3500000
. Tapi kemudian kita lupa bahwa kita perlu melakukan push sebuah karakter a
. Di saat kita melakukan hal tersebut, Rust melihat bahwa kita memerlukan space lebih dan memberikan kita ukuran dua kali lipat dari sebelumnya: sekarang ia menjadi 7000000
. Whoops! Sehingga kita menggunakan .shrink_to_fit()
lagi dan sekarang kapasitasnya kembali turun menjadi 3500001
.
.pop()
bekerja juga pada String
, sama seperti saat digunakan pada Vec
.
fn main() { let mut my_string = String::from(".daer ot drah tib elttil a si gnirts sihT"); loop { let pop_result = my_string.pop(); match pop_result { Some(character) => print!("{}", character), None => break, } } }
Hasil cetaknya adalah This string is a little bit hard to read.
karena ia dimulai dari karakter yang terakhir.
.retain()
adalah method yang menggunakan closure, yang mana String
jarang sekali memiliki method seperti ini. Ia mirip seperti .filter()
untuk iterator.
fn main() { let mut my_string = String::from("Age: 20 Height: 194 Weight: 80"); my_string.retain(|character| character.is_alphabetic() || character == ' '); // Tetap simpan jika ia adalah huruf atau spasi dbg!(my_string); // Mari kali ini kita iseng menggunakan dbg!() menggantikan println! }
Hasilnya adalah:
[src\main.rs:4] my_string = "Age Height Weight "
OsString and CString
std::ffi
adalah bagian dari std
yang membantu Anda untuk menghubungkan Rust dengan bahasa lain atau operating system yang lain. Ia memiliki type seperti OsString
dan CString
, yang mana mirip seperti String
yang ada pada operating system atau String
untuk bahasa C. Masing-masing dari mereka memiliki type &str
nya sendiri juga: yaitu OsStr
dan CStr
. ffi
adalah singkatan dari "foreign function interface".
Anda bisa menggunakan OsString
di saat Anda bekerja dengan operating system yang tidak memiliki Unicode. Semua string di Rust adalah unicode. Hanya saja, tidak semua operating system memilikinya. Ini adalah penjelasan sederhana dari standard library tentang mengapa kita perlu OsString
:
- String di Unix (Linux, etc.) mungkin saja memiliki banyak byte yang sama sekali tidak memiliki angka 0. Dan terkadang Anda membacanya sebagai Unicode UTF-8.
- String di Windows bisa saja dibuat dari random 16-bit yang tidak memiliki angka 0.Dan terkadang Anda membacanya sebagai Unicode UTF-16.
- Di Rust, string selalu valid sebagai UTF-8, yang mungkin saja mengandung angka 0.
Jadi OsString
dibuat agar bisa dibaca oleh semua operating system tersebut.
Anda bisa melakukan semua hal-hal umum dengan menggunakan OsString
seperti OsString::from("Write something here")
. Ia juga memiliki method .into_string()
yang mana ia akan mencoba untuk mengubah OsString
menjadi regular String
. Ia mengembalikan Result
, namun bagian Err
nya adalah OsString
:
#![allow(unused)] fn main() { // 🚧 pub fn into_string(self) -> Result<String, OsString> }
Jadi jika ia tidak bekerja, Anda akan kembali mendapatkan OsString
. Anda tidak bisa menggunakan .unwrap()
karena ia akan panic, tapi Anda bisa menggunakan match
untuk kembali mendapatkan OsString
. Mari kita coba dengan cara memanggil method yang sama sekali tidak ada.
use std::ffi::OsString; fn main() { // ⚠️ let os_string = OsString::from("This string works for your OS too."); match os_string.into_string() { Ok(valid) => valid.thth(), // Compiler: "Apa ini .thth()??" Err(not_valid) => not_valid.occg(), // Compiler: "Apa ini .occg()??" } }
Kemudian compiler memberitahu kita persis apa yang ingin kita ketahui:
error[E0599]: no method named `thth` found for struct `std::string::String` in the current scope
--> src/main.rs:6:28
|
6 | Ok(valid) => valid.thth(),
| ^^^^ method not found in `std::string::String`
error[E0599]: no method named `occg` found for struct `std::ffi::OsString` in the current scope
--> src/main.rs:7:37
|
7 | Err(not_valid) => not_valid.occg(),
| ^^^^ method not found in `std::ffi::OsString`
Kita bisa melihat bahwa type dari valid
adalah String
dan type dari not_valid
adalah OsString
.
mem
std::mem
memiliki method-method yang menarik. Kita sudah melihatnya beberapa, misalnya .size_of()
, .size_of_val()
dan .drop()
:
use std::mem; fn main() { println!("{}", mem::size_of::<i32>()); let my_array = [8; 50]; println!("{}", mem::size_of_val(&my_array)); let mut some_string = String::from("You can drop a String because it's on the heap"); mem::drop(some_string); // some_string.clear(); jika kita melakukan ini, maka programnya akan panic }
Hasilnya adalah:
4
200
Ini adalah beberapa method lainnya di mem
:
swap()
: dengan method ini, Anda bisa mengubah value diantara dua variabel. Anda perlu menggunakan mutable reference di masing-masing variabel tersebut untuk melakukannya. Ini sangat berguna ketika Anda memiliki dua variabel yang ingin Anda tukar valuenya dan Rust tidak mengizinkannya karena adanya borrowing rules. Atau juga di saat Anda ingin menukar dua variabel dengan cepat.
Berikut adalah contohnya:
use std::{mem, fmt}; struct Ring { // Buat sebuah ring dari Lord of the Rings owner: String, former_owner: String, seeker: String, // seeker artinya "orang yang mencari-cari cincin tersebut" } impl Ring { fn new(owner: &str, former_owner: &str, seeker: &str) -> Self { Self { owner: owner.to_string(), former_owner: former_owner.to_string(), seeker: seeker.to_string(), } } } impl fmt::Display for Ring { // Display untuk menampilkan siapa yang memilikinya dan siapa yang menginginkannya fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{} has the ring, {} used to have it, and {} wants it", self.owner, self.former_owner, self.seeker) } } fn main() { let mut one_ring = Ring::new("Frodo", "Gollum", "Sauron"); println!("{}", one_ring); mem::swap(&mut one_ring.owner, &mut one_ring.former_owner); // Gollum mendapatkan cincinnya kembali println!("{}", one_ring); }
Hasilnya adalah:
Frodo has the ring, Gollum used to have it, and Sauron wants it
Gollum has the ring, Frodo used to have it, and Sauron wants it
replace()
: mirip seperti swap, dan sebenarnya di dalamnya memang menggunakan swap, seperti yang Anda lihat:
#![allow(unused)] fn main() { pub fn replace<T>(dest: &mut T, mut src: T) -> T { swap(dest, &mut src); src } }
Jadi sebenarnya ia hanya melakukan swap dan kemudian mengembalikan item yang lain. Dengan method ini, Anda mengganti value dengan value lain yang Anda masukkan. Dan karena ia mengembalikan value yang terdahulu, jadi Anda harus menggunakan let
. Beginilah contohnya.
use std::mem; struct City { name: String, } impl City { fn change_name(&mut self, name: &str) { let old_name = mem::replace(&mut self.name, name.to_string()); println!( "The city once called {} is now called {}.", old_name, self.name ); } } fn main() { let mut capital_city = City { name: "Constantinople".to_string(), }; capital_city.change_name("Istanbul"); }
Hasilnya adalah The city once called Constantinople is now called Istanbul.
.
Ada juga function yang bernama .take()
yang mirip dengan .replace()
, tapi ia menyisakan default value pada itemnya. Anda akan mengingat bahwa default value biasanya adalah sesuatu yang bernilai 0, "", dan lain-lain. Inilah signaturenya:
#![allow(unused)] fn main() { // 🚧 pub fn take<T>(dest: &mut T) -> T where T: Default, }
Sehingga Anda bisa melakukan hal seperti berikut:
use std::mem; fn main() { let mut number_vec = vec![8, 7, 0, 2, 49, 9999]; let mut new_vec = vec![]; number_vec.iter_mut().for_each(|number| { let taker = mem::take(number); new_vec.push(taker); }); println!("{:?}\n{:?}", number_vec, new_vec); }
Dan sebagaimana yang Anda lihat, ia menggantikan semua angka dengan 0: tidak ada index yang dihapus.
[0, 0, 0, 0, 0, 0]
[8, 7, 0, 2, 49, 9999]
Tentu saja, untuk type yang Anda buat, Anda bisa mengimplementasikan Default
ke apapun yang Anda inginkan. Mari kita lihat contoh dimana kita memiliki sebuah Bank
dan sebuah Robber
. Setiap si maling merampok Bank
, ia merampok uangnya dari desk. Namun si desk bisa mengambil uang lagi dari brankas kapanpun, sehingga ia selalu memiliki 50. Kita akan membuat type kita sendiri untuk kasus ini, sehingga ia selalu memiliki 50. Beginilah codenya:
use std::mem; use std::ops::{Deref, DerefMut}; // Kita akan menggunakan ini untuk mendapatkan fitur dari u32 struct Bank { money_inside: u32, money_at_desk: DeskMoney, // Ini adalah type "smart pointer" yang kita buat. Ia memiliki nilai defaultnya sendiri, namun ia juga akan menggunakan u32 } struct DeskMoney(u32); impl Default for DeskMoney { fn default() -> Self { Self(50) // defaultnya selalu bernilai 50, bukan 0 } } impl Deref for DeskMoney { // Dengan ini, kita bisa mengakses u32 menggunakan * type Target = u32; fn deref(&self) -> &Self::Target { &self.0 } } impl DerefMut for DeskMoney { // Dan dengan ini, kita bisa menambahkan, mengurangi, dst. fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } impl Bank { fn check_money(&self) { println!( "There is ${} in the back and ${} at the desk.\n", self.money_inside, *self.money_at_desk // Gunakan * sehingga kita bisa mencetak u32 ); } } struct Robber { money_in_pocket: u32, } impl Robber { fn check_money(&self) { println!("The robber has ${} right now.\n", self.money_in_pocket); } fn rob_bank(&mut self, bank: &mut Bank) { let new_money = mem::take(&mut bank.money_at_desk); // Disini, ia akan mengambil uangnya, dan meninggalkan 50 karena itu merupakan nilai defaultnya self.money_in_pocket += *new_money; // Gunakan * karena kita hanya bisa menambahkan u32. DeskMoney tidak bisa melakukan pertambahan bank.money_inside -= *new_money; // Disini juga println!("She robbed the bank. She now has ${}!\n", self.money_in_pocket); } } fn main() { let mut bank_of_klezkavania = Bank { // Buat sebuah bank money_inside: 5000, money_at_desk: DeskMoney(50), }; bank_of_klezkavania.check_money(); let mut robber = Robber { // Buat seorang perampok money_in_pocket: 50, }; robber.check_money(); robber.rob_bank(&mut bank_of_klezkavania); // Rampok, kemudian periksa uangnya robber.check_money(); bank_of_klezkavania.check_money(); robber.rob_bank(&mut bank_of_klezkavania); // Lakukan lagi robber.check_money(); bank_of_klezkavania.check_money(); }
Hasil cetaknya adalah:
There is $5000 in the back and $50 at the desk.
The robber has $50 right now.
She robbed the bank. She now has $100!
The robber has $100 right now.
There is $4950 in the back and $50 at the desk.
She robbed the bank. She now has $150!
The robber has $150 right now.
There is $4900 in the back and $50 at the desk.
Anda bisa melihat bahwa di desk selalu ada $50.
prelude
Standard library juga memiliki prelude. Inilah mengapa Anda tidak perlu menuliskan sesuatu seperti use std::vec::Vec
untuk membuat sebuah Vec
. Anda bisa melihat semua itemnya di sini, dan kita sudah mengetahui hampir semuanya:
std::marker::{Copy, Send, Sized, Sync, Unpin}
. Anda belum pernah melihatUnpin
sebelumnya, karena ia sudah digunakan hampir di semua type (sepertiSized
, yang juga sangat umum). "Pin" berarti tidak membiarkan sesuatu bergerak. Pada kasus ini,Pin
berarti bahwa ia tidak bisa berpindah di memory, namun banyak item yang memilikiUnpin
, sehingga Anda bisa melakukannya. Inilah mengapa function-function sepertistd::mem::replace
bekerja, karena mereka tidak dalam kondisi ter-pin.std::ops::{Drop, Fn, FnMut, FnOnce}
.std::mem::drop
std::boxed::Box
.std::borrow::ToOwned
. Anda sudah melihat ini sedikit di saat mempelajariCow
, yang mana bisa mengambil borrowed value dan membuat kepemilikannya menjadi owned. Ia menggunakan method.to_owned()
untuk melakukan. Anda juga bisa menggunakan.to_owned()
pada&str
untuk mendapatkanString
, dan sama pula pada borrowed value lainnya.std::clone::Clone
std::cmp::{PartialEq, PartialOrd, Eq, Ord}
.std::convert::{AsRef, AsMut, Into, From}
.std::default::Default
.std::iter::{Iterator, Extend, IntoIterator, DoubleEndedIterator, ExactSizeIterator}
. Sebelumnya kita menggunakan.rev()
untuk iterator: Ini sebenarnya membuatDoubleEndedIterator
.ExactSizeIterator
hanyalah sesuatu seperti0..10
: ia sudah tahu bahwa ia memiliki.len()
sebesar 10. Iterator yang lain tidak mengetahui panjangnya secara pasti.std::option::Option::{self, Some, None}
.std::result::Result::{self, Ok, Err}
.std::string::{String, ToString}
.std::vec::Vec
.
Bagaimana jika Anda tidak menginginkan prelude karena suatu hal tertentu? Cukup tambahkan attribute #![no_implicit_prelude]
. Mari kita coba dan kita lihat teguran dari compiler:
// ⚠️ #![no_implicit_prelude] fn main() { let my_vec = vec![8, 9, 10]; let my_string = String::from("This won't work"); println!("{:?}, {}", my_vec, my_string); }
Sekarang Rust tidak mengerti apa yang ingin kita lakukan:
error: cannot find macro `println` in this scope
--> src/main.rs:5:5
|
5 | println!("{:?}, {}", my_vec, my_string);
| ^^^^^^^
error: cannot find macro `vec` in this scope
--> src/main.rs:3:18
|
3 | let my_vec = vec![8, 9, 10];
| ^^^
error[E0433]: failed to resolve: use of undeclared type or module `String`
--> src/main.rs:4:21
|
4 | let my_string = String::from("This won't work");
| ^^^^^^ use of undeclared type or module `String`
error: aborting due to 3 previous errors
Jadi untuk code ini, Anda perlu memberitahukan Rust untuk menggunakan crate extern
(external) bernama std
, dan kemudian menyebutkan item-item yang Anda ingin gunakan. Berikut ini adalah semua yang harus kita lakukan hanya untuk membuat sebuah Vec dan String, dan juga mencetaknya:
#![no_implicit_prelude] extern crate std; // Sekarang Anda perlu memberitahukan kepada Rust bahwa Anda ingin menggunakan crate bernama std use std::vec; // kita memerlukan macro vec use std::string::String; // dan juga string use std::convert::From; // dan ini berguna untuk mengkonversi dari &str ke String use std::println; // dan ini untuk print fn main() { let my_vec = vec![8, 9, 10]; let my_string = String::from("This won't work"); println!("{:?}, {}", my_vec, my_string); }
Dan sekarang codenya berjalan. Hasilnya adalah [8, 9, 10], This won't work
. Jadinya, Anda bisa melihat mengapa Rust menggunakan prelude. Namun jika Anda ingin, Anda tidak perlu menggunakannya. Dan bahkan Anda bisa menggunakan #![no_std]
(kita pernah melihatnya sekali sebelumnya) ketika Anda bahkan tidak bisa menggunakan sesuatu seperti stack memory. Tetapi seringnya kita tidak perlu berpikir tentang tidak menggunakan prelude atau std
sama sekali.
Jadi mengapa sebelumnya kita belum pernah melihat keyword extern
? Ini karena kita tidak terlalu memerlukannya lagi. Di Rust versi lama, di saat kita memanggil external crate, kita harus menggunakannya. Jadi, di Rust versi lama, untuk menggunakan rand
, Anda perlu menuliskan:
#![allow(unused)] fn main() { extern crate rand; }
dan kemudian statement use
untuk mod, trait, dll. yang ingin Anda gunakan. Namun compiler Rust sekarang tidak membutuhkan bantuan ini lagi - Anda cukup menggunakan use
dan compiler tahu dimana menemukannya. Jadi Anda hampir tidak pernah membutuhkan extern crate
lagi. Tapi di code Rust yang ditulis oleh orang lain, mungkin Anda masih melihatnya di bagian atas codenya.
time
std::time
adalah dimana Anda bisa mendapatkan function untuk waktu. (Jika Anda menginginkan lebih banyak function, crate seperti chrono
bisa digunakan.) Function paling sederhana adalah mengambil system time dengan Instant::now()
.
use std::time::Instant; fn main() { let time = Instant::now(); println!("{:?}", time); }
Jika Anda mencetaknya, Anda akan mendapatkan sesuatu seperti berikut: Instant { tv_sec: 2738771, tv_nsec: 685628140 }
. Itu adalah detik dan nanosecond, namun itu tidaklah terlalu berguna. Misalnya, jika Anda melihat pada 2738771 detik (dicetak pada bulan Agustus), itu adalah 31.70 hari. Itu sama sekali tidak ada hubungannya dengan bulan atau hari dalam setahun. Namun laman tentang Instant
memberi tahu kita bahwa Instant
tidaklah berguna jika dipakai begitu saja. Dikatakan bahwa "opaque and useful only with Duration." Opaque berarti "Anda tidak bisa memahaminya", dan duration/durasi artinya "berapa lama waktu sudah berlalu". Sehingga ia hanya berguna di saat kita perlu melakukan sesuatu seperti membandingkan waktu.
Jika Anda melihat pada trait-trait yang berada di sebelah kiri, salah satu dari trait tersebut adalah Sub<Instant>
. Yang berarti kita bisa menggunakan -
untuk mengurangkannya dengan yang lain. Dan saat kita klik pada [src] untuk melihat apa yang ia lakukan, maka kita akan melihat code berikut:
#![allow(unused)] fn main() { impl Sub<Instant> for Instant { type Output = Duration; fn sub(self, other: Instant) -> Duration { self.duration_since(other) } } }
Jadinya, ia mengambil Instant
dan menggunakan method .duration_since()
untuk mendapatkan Duration
. Mari kita coba untuk mencetaknya. Kita akan membuat dua buah Instant::now()
tepat setelah satu sama lain (time2 ditulis setelah time1), kemudian kita akan membuat programnya sibuk untuk sementara waktu. Kemudian kita akan membuat satu lagi Instant::now()
. Akhirnya kita akan melihat berapa lama waktu yang dibutuhkan.
use std::time::Instant; fn main() { let time1 = Instant::now(); let time2 = Instant::now(); // time2 dibuat tepat setelah dibuatnya time1 let mut new_string = String::new(); loop { new_string.push('წ'); // buat Rust melakukan push karakter Georgian ini ke dalam String if new_string.len() > 100_000 { // lakukan sampai panjangnya 100,000 byte break; } } let time3 = Instant::now(); println!("{:?}", time2 - time1); println!("{:?}", time3 - time1); }
Hasilnya adalah seperti berikut:
1.025µs
683.378µs
Jadi ada jeda 1 microsecond vs. 683 microsecond. Kita bisa lihat bahwa Rust memerlukan waktu untuk melakukannya.
Ada satu hal menarik yang bisa kita lakukan dengan sebuah Instant
. Kita bisa mengubahnya menjadi String
dengan menggunakan format!("{:?}", Instant::now());
. Begini codenya:
use std::time::Instant; fn main() { let time1 = format!("{:?}", Instant::now()); println!("{}", time1); }
Hasil cetaknya adalah seperti ini: Instant { tv_sec: 2740773, tv_nsec: 632821036 }
. Tentu saja itu tidak bergunak, namun jika kita menggunakan .iter()
dan .rev()
dan .skip(2)
, kita bisa membuang }
dan
. Kita bisa menggunakannya untuk membuat sebuah random number generator.
use std::time::Instant; fn bad_random_number(digits: usize) { if digits > 9 { panic!("Random number can only be up to 9 digits"); } let now = Instant::now(); let output = format!("{:?}", now); output .chars() .rev() .skip(2) .take(digits) .for_each(|character| print!("{}", character)); println!(); } fn main() { bad_random_number(1); bad_random_number(1); bad_random_number(3); bad_random_number(3); }
Hasilnya adalah seperti berikut:
6
4
967
180
Functionnya kita beri nama bad_random_number
karena itu bukanlah cara yang bagus untuk membuat random number generator. Rust memiliki crates yang lebih baik untuk membuat angka random dengan code yang lebih singkat dari rand
contohnya fastrand
. Namun ini adalah contoh yang baik tentang bagaimana kita bisa menggunakan imajinasi kita membuat sesuatu dengan menggunakan Instant
.
Jika Anda memiliki thread, Anda bisa menggunakan std::thread::sleep
untuk membuatnya stop untuk sementara waktu. Di saat Anda melakukan ini, Anda perlu memberikannya durasi. Anda tidak perlu membuat membuat lebih dari satu thread untuk melakukan ini karena sebenarnya setiap program berjalan dengan menggunakan setidaknya satu thread. sleep
memerlukan Duration
, sehingga ia bisa mengetahui seberapa lama ia sleep. Anda bisa memilih unitnya (satuannya) seperti ini: Duration::from_millis()
, Duration::from_secs
, dll. Seperti inilah contohnya:
use std::time::Duration; use std::thread::sleep; fn main() { let three_seconds = Duration::from_secs(3); println!("I must sleep now."); sleep(three_seconds); println!("Did I miss anything?"); }
Hasilnya adalah:
I must sleep now.
Did I miss anything?
namun thread tidak melakukan apapun selama tiga detik. Anda biasanya menggunakan .sleep()
di saat Anda menggunakan banyak thread yang perlu mencoba melakukan banyak hal, misalnya memeriksa koneksi. Anda tidak menginginkan thread menggunakan processor Anda untuk mencoba 100,000 kali dalam sedetik ketika Anda hanya ingin memeriksanya sewaktu-waktu. Jadi, Anda dapat menyetel Duration
, dan ia akan mencoba melakukan tugasnya setiap kali threadnya mulai aktif.
Other macros
Mari kita melihat-lihat beberapa macro yang lain.
unreachable!()
Macro ini mirip seperti todo!()
, namun untuk code yang tidak pernah Anda tuliskan. Mungkin Anda memiliki match
di dalam enum yang Anda sendiri tahu bahwa kondisinya tidak akan memilih salah satu arm pun, jadi codenya sama sekali tidak pernha bisa dijangkau (reached). Jika demikian, Anda bisa menuliskan unreachable!()
sehingga compiler tahu bagian itu diabaikan saja.
Sebagai contoh, anggap saja Anda memiliki program yang menuliskan sesuatu di saat Anda memilih tempat tinggal. Lokasinya ada di Ukraina, dan semua kota-kotanya baik-baik saja kecuali Chernobyl. Program yang Anda buat tidak akan mengizinkan siapapun memilih Chernobyl, karena kota itu bukanlah tempat yang layak untuk ditinggali untuk sekarang ini. Tapi, enumnya sudah lama dibuat oleh orang lain, dan kita tidak bisa mengubahnya. Jadi di arm yang ada pada match
, Anda bisa menggunakan macro unreachable!()
disini. Codenya terlihat seperti ini:
enum UkrainePlaces { Kiev, Kharkiv, Chernobyl, // Anggap saja kita tidak bisa mengubah enumnya - Chernobyl akan selalu ada di dalam enum ini Odesa, Dnipro, } fn choose_city(place: &UkrainePlaces) { use UkrainePlaces::*; match place { Kiev => println!("You will live in Kiev"), Kharkiv => println!("You will live in Kharkiv"), Chernobyl => unreachable!(), Odesa => println!("You will live in Odesa"), Dnipro => println!("You will live in Dnipro"), } } fn main() { let user_input = UkrainePlaces::Kiev; // Anggap saja inputan dari user dibuat dari suatu function. User tidak akan bisa memilih Chernobyl, apapun yang terjadi choose_city(&user_input); }
Hasilnya adalah You will live in Kiev
.
unreachable!()
juga baik digunakan untuk membaca code karena ia akan mengingatkan Anda bahwa ada beberapa bagian dari code yang kondisinya tidak bisa dijangkau (unreachable). Anda harus pastikan bahwa code tersebut memang benar tidak bisa dijangkau. Karena jika Anda menggunakan unreachable!()
, padahal armnya bisa dijangkau, maka programnya akan panic.
Juga, jika Anda memiliki code yang unreachable, maka compiler akan mengetahuinya, dan memberitahukannya ke Anda. Seperti ini contohnya:
fn main() { let true_or_false = true; match true_or_false { true => println!("It's true"), false => println!("It's false"), true => println!("It's true"), // Oops, kita menuliskan true lagi disini } }
Compiler akan mengatakan:
warning: unreachable pattern
--> src/main.rs:7:9
|
7 | true => println!("It's true"),
| ^^^^
|
Sedangkan unreachable!()
ini diperuntukkan di saat compiler tidak mengetahuinya, seperti contoh kita yang di awal tersebut (tentang Chernobyl).
column!
, line!
, file!
, module_path!
Empat macro ini mirip seperti dbg!()
karena Anda memasukannya ke code Anda untuk memberikan informasi mengenai debug. Namun ia tidak mengambil variabel apapun - Anda cukup menggunakan mereka dengan bracket (tanda kurung) dan tidak ada yang lain. Keempatnya mudah untuk dipelajari:
column!()
memberikan Anda informasi kolom dimana Anda menuliskannya,file!()
memberikan Anda informasi nama file dimana Anda menuliskannya,line!()
memberikan Anda informasi line/baris dimana Anda menuliskannya, danmodule_path!()
memberikan Anda informasi di module mana ia berada.
Code berikut ini menunjukkan keempatnya dalam contoh yang sederhana. Kita akan menganggap ada lebih banyak code (mod di dalam mod), karena itulah alasannya kita ingin menggunakan macro module_path!()
. Anda bisa membayangkan sebuah program Rust yang besar yang dibuat dengan banyak mod dan file.
pub mod something { pub mod third_mod { pub fn print_a_country(input: &mut Vec<&str>) { println!( "The last country is {} inside the module {}", input.pop().unwrap(), module_path!() ); } } } fn main() { use something::third_mod::*; let mut country_vec = vec!["Portugal", "Czechia", "Finland"]; // lakukan sesuatu println!("Hello from file {}", file!()); // lakukan sesuatu println!( "On line {} we got the country {}", line!(), country_vec.pop().unwrap() ); // lakukan sesuatu println!( "The next country is {} on line {} and column {}.", country_vec.pop().unwrap(), line!(), column!(), ); // ada banyak code di bagian ini print_a_country(&mut country_vec); }
Hasil cetaknya adalah:
Hello from file src/main.rs
On line 23 we got the country Finland
The next country is Czechia on line 32 and column 9.
The last country is Portugal inside the module rust_book::something::third_mod
cfg!
Kita mengetahui bahwa kita bisa menggunakan attribute seperti #[cfg(test)]
dan #[cfg(windows)]
untuk memberitahukan compiler apa yang harus dilakukan dalam kasus tertentu. Di saat Anda memiliki test
, ia akan menjalankan code ketika kita menjalankan Rust dalam mode testing (jika ia ada di dalam komputer Anda, maka Anda perlu mengetikkan cargo test
). Dan di saat Anda menggunakan windows
, ia akan menjalankan code jika user menggunakan Windows. Tapi mungkin Anda hanya ingin mengubah sedikit code tergantung pada operating systemnya, dll. Di saat seperti itulah macro ini menjadi berguna. ia akan me-return bool
.
fn main() { let helpful_message = if cfg!(target_os = "windows") { "backslash" } else { "slash" }; println!( "...then in your hard drive, type the directory name followed by a {}. Then you...", helpful_message ); }
Hasil cetaknya akan berbeda, berdasarkan operating system yang Anda gunakan. Rust Playground berjalan di atas Linux, sehingga ia akan mencetak:
...then in your hard drive, type the directory name followed by a slash. Then you...
cfg!()
berfungsi untuk setiap jenis konfigurasi. Berikut adalah contoh dari sebuah function yang berjalan dengan cara yang berbeda saat Anda menjalankannya di dalam test.
#[cfg(test)] // cfg! akan bisa mencari kata test mod testing { use super::*; #[test] fn check_if_five() { assert_eq!(bring_number(true), 5); // Function bring_number() ini semestinya me-return 5 } } fn bring_number(should_run: bool) -> u32 { // Function ini memerlukan bool untuk mengetahui apakah ia harus dijalankan if cfg!(test) && should_run { // jika ia semestinya dijalankan dan memiliki konfigurasi test, maka akan me-return 5 5 } else if should_run { // if ia bukan test namun ia harus dijalankan, maka cetak sesuatu. Jika Anda menjalankan test. ia akan mengabaikan statement println! println!("Returning 5. This is not a test"); 5 } else { println!("This shouldn't run, returning 0."); // sebaliknya, return 0 0 } } fn main() { bring_number(true); bring_number(false); }
Sekarang ia akan berjalan secara berbeda, tergantung dari konfigurasinya. Jika Anda hanya menjalankan programnya, hasil cetaknya adalah seperti ini:
Returning 5. This is not a test
This shouldn't run, returning 0.
Namun jika Anda menjalankannya di dalam test mode (cargo test
untuk Rust yang ada pada komputer), maka ia akan menjalankan testnya. Dan karena di kasus ini testnya selalu me-return 5, testnya akan pass.
running 1 test
test testing::check_if_five ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out