Type inference

See this chapter on YouTube

Type inference artinya adalah, jika Anda tidak memberitahukan type yang Anda gunakan, tapi compiler bisa memilihkannya, maka compiler yang nantinya akan memilihkan typenya. Compiler Rust selalu perlu mengetahui type dari sebuah variabel, namun Anda tidak selalu perlu untuk memberitahukannya. Dan sebenarnya, biasanya Anda memang tidak perlu memberitahukannya. Sebagai contoh, pada chapter sebelumnya, untuk let my_number = 8, my_number akan menjadi i32. Ini dikarenakan compiler secara default akan memilih i32 untuk integer jika Anda tidak memberitahukannya. Namun jika Anda memberitahukannya, seperti let my_number: u8 = 8, maka itu akan membuat my_number bertype u8, karena Anda memberitahu ke compiler untuk menggunakan type u8.

Jadi, sebenarnya compiler bisa memilihkan type untuk kita. Tapi terkadang kita perlu memberitahukannya ke compiler, karena 2 alasan berikut:

  1. Kita membuat sesuatu yang lumayan kompleks dan compiler tidak tahu type yang kita inginkan.
  2. Kita menginginkan type yang berbeda (contohnya, Anda ingin i128, bukan i32).

Untuk menambahkan type, tambahkan colon (titik dua) setelah nama variabel dan juga type yang Anda perlukan.

fn main() {
    let small_number: u8 = 10;
}

Untuk angka, Anda bisa menyebutkan typenya setelah angka. Anda sama sekali tidak memerlukan spasi. Cukup dituliskan tepat setelah angkanya.

fn main() {
    let small_number = 10u8; // 10u8 = 10 of type u8
}

Anda juga bisa menambahkan _ jika Anda ingin membuat angkanya menjadi mudah untuk dibaca.

fn main() {
    let small_number = 10_u8; // Ini menjadi lebih mudah untuk dibaca
    let big_number = 100_000_000_i32; // 100 juta menjadi mudah dibaca dengan _
}

Underscore (_) tidak akan mengubah nilainya. Ia berguna sekedar untuk mempermudah kita dalam pembacaan code. Dan sama sekali tidak masalah seberapa _ yang kita gunakan:

fn main() {
    let number = 0________u8;
    let number2 = 1___6______2____4______i32;
    println!("{}, {}", number, number2);
}

Ia akan mencetak 0, 1624.

Floats

Floats adalah angka berkoma (memiliki decimal points). 5.5 adalah float, dan 6 adalah integer. 5.0 juga adalah sebuah float, dan 5. itu juga adalah float.

fn main() {
    let my_float = 5.; // Rust melihat ada . dan tahu bahwa itu adalah sebuah float
}

Namun, di Rust type ini bukan disebut sebagai float, mereka disebut sebagai f32 dan f64. Ini sama seperti integer, yaitu angka setelah f menunjukkan panjang bit yang digunakan. Jika Anda tidak menuliskan typenya, Rust akan memilihkan f64.

Tentu saja, hanya float dengan type yang sama yang bisa digunakan bersama-sama. Jadi kita tidak bisa menambahkan f32 ke f64.

fn main() {
    let my_float: f64 = 5.0; // Ini adalah f64
    let my_other_float: f32 = 8.5; // Ini adalah f32

    let third_float = my_float + my_other_float; // ⚠️
}

Jika Anda coba untuk menjalankan ini, compiler Rust akan mengatakan:

error[E0308]: mismatched types
 --> src\main.rs:5:34
  |
5 |     let third_float = my_float + my_other_float;
  |                                  ^^^^^^^^^^^^^^ expected `f64`, found `f32`

Compiler memberitahukan "expected (type), found (type)" di saat Anda menggunakan type yang salah (tidak sesuai). Compiler akan memproses code Anda seperti yang dituliskan pada komentar program dibawah ini:

fn main() {
    let my_float: f64 = 5.0; // Compiler melihat sebuah variabel f64
    let my_other_float: f32 = 8.5; // Compiler melihat sebuah f32. Ini merupakan type yang berbeda.
    let third_float = my_float + // Anda ingin menambahkan my_float dengan sesuatu, maka semestinya adalah f64 ditambahakan dengan f64. Maka, sekarang compiler berekspektasi bahwa ia akan ditambahkan dengan f64...
    let third_float = my_float + my_other_float;  // ⚠️ namun compiler justru menemukan f32. Sehingga compiler tidak bisa menambahkan keduanya.
}

Jadi, saat kita melihat "expected (type), found (type)", kita harus menemukan mengapa compiler berekspektasi/membutuhkan type yang berbeda.

Tentu saja, dengan angka float yang sederhana, code tersebut akan mudah untuk diperbaiki. Anda bisa melakukan cast f32 sebagai f64 dengan menggunakan as:

fn main() {
    let my_float: f64 = 5.0;
    let my_other_float: f32 = 8.5;

    let third_float = my_float + my_other_float as f64; // my_other_float as f64 = gunakan my_other_float seperti type f64
}

Atau dengan cara yang lebih mudah, hapus pendeklarasian typenya. ("pendeklarasian type" = "memberitahukan compiler Rust untuk menggunakan type yang dituliskan") Rust akan memilihkan typenya sehingga keduanya bisa melakukan operasi penjumlahan.

fn main() {
    let my_float = 5.0; // Rust akan memilih f64
    let my_other_float = 8.5; // Di bagian ini, Rust juga akan memilih f64

    let third_float = my_float + my_other_float;
}

Compiler Rust cukup cerdas dalam hal ini. Dan dia tidak akan memilih f64 jika kita memerlukan f32:

fn main() {
    let my_float: f32 = 5.0;
    let my_other_float = 8.5; // Biasanya, Rust akan memilih f64,

    let third_float = my_float + my_other_float; // tapi sekarang compiler Rust tahu bahwa kita perlu menambahkannya ke sebuah f32. Maka, compiler Rust akan memilih f32 untuk variabel my_other_float
}