Box around traits
Box
sangatlah berguna untuk mengembalikan trait. Kita mengetahui bahwa kita bisa menulis traits pada generic functions seperti pada contoh di bawah ini:
use std::fmt::Display; struct DoesntImplementDisplay {} fn displays_it<T: Display>(input: T) { println!("{}", input); } fn main() {}
Function display_ithanya mengambil inputan yang memiliki trait Display
, sehingga ia tidak bisa menerima struct DoesntImplementDisplay
. Tapi ia bisa mengambil type lain seperti String
.
Anda juga melihat bahwa kita bisa menggunakan impl Trait
untuk mengembalikan trait lainnya atau juga closure. Box
bisa digunakan dengan cara yang sama. Anda bisa menggunakan Box
, karena jika tidak menggunakannya, compiler tidak akan tahu ukuran valuenya. Contoh ini menunjukkan bahwa sebuah trait bisa digunakan pada sesuatu dengan size berapa pun:
#![allow(dead_code)] // memberitahu compiler untuk tidak memberikan warning use std::mem::size_of; // ini memberikan informasi tentang ukuran dari sebuah type trait JustATrait {} // kita akan mengimplementasikan ini ke semuanya enum EnumOfNumbers { I8(i8), AnotherI8(i8), OneMoreI8(i8), } impl JustATrait for EnumOfNumbers {} struct StructOfNumbers { an_i8: i8, another_i8: i8, one_more_i8: i8, } impl JustATrait for StructOfNumbers {} enum EnumOfOtherTypes { I8(i8), AnotherI8(i8), Collection(Vec<String>), } impl JustATrait for EnumOfOtherTypes {} struct StructOfOtherTypes { an_i8: i8, another_i8: i8, a_collection: Vec<String>, } impl JustATrait for StructOfOtherTypes {} struct ArrayAndI8 { array: [i8; 1000], // ukuran yang ini tentunya akan besar an_i8: i8, in_u8: u8, } impl JustATrait for ArrayAndI8 {} fn main() { println!( "{}, {}, {}, {}, {}", size_of::<EnumOfNumbers>(), size_of::<StructOfNumbers>(), size_of::<EnumOfOtherTypes>(), size_of::<StructOfOtherTypes>(), size_of::<ArrayAndI8>(), ); }
Jika kita mencetak ukuran dari setiap enum dan struct di atas, kita mendapatkan 2, 3, 32, 32, 1002
. Sehingga jika Anda melakukan hal seperti dibawah ini, ia akan mencetak error:
#![allow(unused)] fn main() { // ⚠️ fn returns_just_a_trait() -> JustATrait { let some_enum = EnumOfNumbers::I8(8); some_enum } }
Compiler akan memberikan pesan:
error[E0746]: return type cannot have an unboxed trait object
--> src\main.rs:53:30
|
53 | fn returns_just_a_trait() -> JustATrait {
| ^^^^^^^^^^ doesn't have a size known at compile-time
Dan ini adalah benar, ukurannya adalah 2, 3, 32, 1002, atau berapapun. Sehingga kita masukkan ia ke dalam Box
. Disini kita juga menambahkan keyword dyn
. dyn
adalah keyword yang menunjukkan bahwa kita berurusan dengan trait, bukan dengan struct atapun yang lainnya.
Sehingga Anda bisa menggunakan functionnya menjadi seperti ini:
#![allow(unused)] fn main() { // 🚧 fn returns_just_a_trait() -> Box<dyn JustATrait> { let some_enum = EnumOfNumbers::I8(8); Box::new(some_enum) } }
Dan sekarang codenya berjalan, karena yang berada di stack adalah Box
dan kita mengetahui ukuran dari Box
.
Anda akan sering melihat hal ini dalam bentuk Box<dyn Error>
, karena terkadang Anda bisa memiliki lebih dari satu kemungkinan error.
Kita bisa membuat dua buah type error untuk menunjukkan ini. Untuk membuat type error (yang resmi disediakan oleh Rust), Anda perlu untuk mengimplementasikan std::error::Error
. Hal ini sangatlah mudah: cukup tuliskan impl std::error::Error {}
. Tapi error juga memerlukan Debug
dan Display
sehinga ia bisa memberikan informasi tentang problem yang muncul. Debug
bisa digunakan dengan mudah menggunakan #[derive(Debug)]
, namun Display
memerlukan method .fmt()
. Kita sudah pernah melakukannya sekali sebelumnya.
Codenya adalah seperti berikut:
use std::error::Error; use std::fmt; #[derive(Debug)] struct ErrorOne; impl Error for ErrorOne {} // ErrorOne adalah sebuah type error dengan trait Debug. Saatnya kita tambahkan trait Display: impl fmt::Display for ErrorOne { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "You got the first error!") // tuliskan pesan errornya } } #[derive(Debug)] // Lakukan hal yang sama dengan ErrorTwo struct ErrorTwo; impl Error for ErrorTwo {} impl fmt::Display for ErrorTwo { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "You got the second error!") } } // Buat sebuah function yang mengembalikan sebuah String atau sebuah error fn returns_errors(input: u8) -> Result<String, Box<dyn Error>> { // Dengan Box<dyn Error>, Anda bisa mengembalikan apapun yang memiliki trait Error match input { 0 => Err(Box::new(ErrorOne)), // Jangan lupa untuk meletakkan type errornya di dalam Box 1 => Err(Box::new(ErrorTwo)), _ => Ok("Looks fine to me".to_string()), // Ini adalah type Result Ok } } fn main() { let vec_of_u8s = vec![0_u8, 1, 80]; // Tiga angka yang akan dicoba dengan function returns_errors for number in vec_of_u8s { match returns_errors(number) { Ok(input) => println!("{}", input), Err(message) => println!("{}", message), } } }
Hasil cetaknya adalah:
You got the first error!
You got the second error!
Looks fine to me
Jika kita tidak memiliki Box<dyn Error>
dan menuliskannya, kita akan mendapatkan problem seperti berikut:
#![allow(unused)] fn main() { // ⚠️ fn returns_errors(input: u8) -> Result<String, Error> { match input { 0 => Err(ErrorOne), 1 => Err(ErrorTwo), _ => Ok("Looks fine to me".to_string()), } } }
Inilah pesan errornya:
21 | fn returns_errors(input: u8) -> Result<String, Error> {
| ^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
Error tidaklah mengejutkan, karena kita tahu bahwa sebuah trait bisa berjalan di banyak struktur, dan setiap dari mereka memiliki ukuran yang berbeda.