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.