Types
Rust memiliki beberapa type, entah berupa angka, karakter, dsb. Beberapa type tersebut tergolong sederhana, sedangkan yang lainnya tergolong lebih rumit. Anda juga bisa membuat type Anda sendiri.
Primitive types
Rust memiliki type sederhana yang biasanya disebut sebagai primitive types (primitive = paling dasar). Kita akan memulainya dengan integer dan char
(karakter). Integer adalah semua angka yang tidak berkoma. Ada 2 type integer:
- Signed integers (Integer bertanda),
- Unsigned integers (Integer tidak bertanda).
Bertanda artinya +
(tanda tambah) dan -
(tanda minus), maka integer bertanda bisa jadi positif atau negatif (contohnya, +8, -8). Namun, integer tidak bertanda hanya dapat menyimpan bilangan bulat positif, karena ia tidak memiliki tanda.
Type-type integer bertanda adalah sebagai berikut: i8
, i16
, i32
, i64
, i128
, dan isize
.
Sedangkan ini adalah type-type integer tidak bertanda: u8
, u16
, u32
, u64
, u128
, dan usize
.
Angka setelah i ataupun u adalah panjang bit yang digunakan untuk menyimpan bilangan, jadi semakin besar angkanya, semakin banyak pula bit yang digunakan. 8 bit = 1 byte, jadi i8
adalah 1 byte, i64
adalah 8 byte, dan seterusnya. Type dengan panjang bit yang lebih lebar bisa menyimpan angka yang lebih besar. Contohnya, u8
bisa menyimpan sampai dengan 255, sedangakan u16
bisa menyimpan sampai dengan 65535. Juga u128
bisa menyimpan sampai dengan 340282366920938463463374607431768211455.
Apa itu isize
dan usize
? Kedua type tersebut menandakan bahwa compiler akan mencocokkan ukuran bit dengan arsitektur komputer anda. (Jumlah bit pada komputer Anda biasanya disebut sebagai arsitektur.) Jadinya isize
dan usize
pada komputer 32-bit adalah i32
dan u32
, juga isize
dan usize
pada komputer 64-bit adalah i64
dan u64
.
Ada banyak alasan mengapa ada banyak sekali type dari integer. Salah satunya adalah performa: angka yang menggunakan byte yang kecil lebih cepat untuk diproses. Contohnya, angka -10 pada i8
representasi binernya adalah 11110110
, namun pada i128
representasi binernya adalah 11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111110110
. Selain itu, ada juga beberapa kegunaan lainnya, seperti:
Karakter di Rust disebut sebagai char
. Setiap char
memiliki angka: huruf A
memiliki nilai 65, sedangkan karakter ε
("kawan" dalam Bahasa Mandarin) memiliki nilai 21451. Angka-angka ini disebut sebagai "Unicode". Unicode yang menggunakan angka yang lebih kecil diperuntukkan bagi karakter yang sering digunakan, seperti A sampai Z, atau digit 0 sampai 9, maupun spasi.
fn main() { let first_letter = 'A'; let space = ' '; // Sebuah spasi di antara ' ' juga adalah sebuah char let other_language_char = 'α'; // Berkat Unicode, bahasa lain seperti Cherokee juga tampil dengan baik let cat_face = 'πΊ'; // Emojis juga adalah char }
Karakter yang seringkali digunakan tersebut kebanyakan memiliki nilai dibawah dari 256. Maka, karakter-karakter tersebut muat jikalau disimpan didalam u8
. Ingat, u8
bisa menyimpan dari 0 sampai dengan 255, yang artinya, totalnya adalah 256. Ini berarti bahwa Rust bisa dengan aman melakukan cast dari u8
ke char
, menggunakan as
. ("Cast u8
ke char
" artinya "perlakukan u8
sebagai char
")
Melakukan Cast menggunakan as
sangatlah berguna karena Rust benar-benar sangat ketat. Rust selalu perlu untuk mengetahui type yang digunakan dan tidak akan membiarkan kita menggunakan tipe data yang berbeda meskipun keduanya berupa integer. Pada contoh dibawah ini, code ini tidak akan berjalan:
fn main() { // main() adalah dimana program Rust mulai berjalan. Sedangkan codenya dituliskan di dalam {} (curly brackets) let my_number = 100; // Kita tidak menuliskan type dari integer tersebut, // sehingga Rust memilih i32. Rust selalu // memilih i32 untuk integer apabila kita tidak // memberitahukan compiler untuk menggunakan type yang berbeda. println!("{}", my_number as char); // β οΈ }
Alasannya adalah sebagai berikut:
error[E0604]: only `u8` can be cast as `char`, not `i32`
--> src\main.rs:3:20
|
3 | println!("{}", my_number as char);
| ^^^^^^^^^^^^^^^^^
Untungnya, kita bisa dengan mudah memperbaiki ini dengan menggunakan as
. Kita tidak bisa melakukan cast i32
sebagai char
, tapi kita bisa cast i32
sebagai u8
. Dan karena itu, kita bisa melakukan yang sama pada u8
ke char
. Maka kita menggunakan as
untuk membuat my_number menjadi u8
, dan di baris baru, kemudian mengubahnya lagi menjadi char
. Jalankan code ini:
fn main() { let my_number = 100; println!("{}", my_number as u8 as char); }
Dan ia akan mencetak d
, karena itu merupakan char
yang bernilai 100.
Cara yang lebih mudah adalah, kita beri tahu saja ke Rust bahwa my_number
itu adalah u8
. Begini caranya:
fn main() { let my_number: u8 = 100; // ubah my_number ke my_number: u8 println!("{}", my_number as char); }
Itu merupakan 2 alasan mengapa ada banyak sekali type integer di Rust. Ini adalah alasan lainnya: usize
adalah type yang digunakan Rust untuk keperluan indexing. (Indexing artinya "yang mana item yang pertama", "yang mana item yang kedua", dan seterusnya.) usize
adalah type yang cocok untuk melakukan indexing, karena:
- Sebuah index tidak bisa negatif, jadi yang diperlukan adalah bilangan tidak bertanda (u)
- Integer yang digunakan harus berukuran besar, karena terkadang kita perlu untuk meng-index banyak hal, tapi
- u64 tidak bisa digunakan dikarenakan komputer 32-bit tidak bisa menggunakan u64.
Jadi Rust menggunakan usize
dan menyerahkan pada komputer kita untuk menentukan integer terbesar yang mampu dijangkau olehnya.
Mari kita pelajari lebih lanjut tentang char
. Dapat dilihat bahwa char
selalu berisi hanya 1 karakter, dan menggunakan ''
, bukan ""
.
Semua chars
menggunakan 4 byte memori, karena sejauh ini 4 bytes cukup untuk menampung hampir semua karakter apapun yang ada sekarang:
- Huruf-huruf dasar dan simbol biasanya memerlukan 1 dari 4 byte:
a b 1 2 + - = $ @
- Aksara lainnya, seperti German Umlaut, memerlukan 2 dari 4 byte:
Γ€ ΓΆ ΓΌ Γ Γ¨ Γ© Γ Γ±
- Aksara Korea, Jepang atau Mandarin memerlukan 3 atau 4 byte:
ε½ μ λ
Di saat menggunakan karakter sebagai bagian dari sebuah string, maka string akan di-encode untuk menggunakan sesedikit mungkin memori yang dibutuhkan untuk setiap karakter.
Kita bisa menggunakan .len()
untuk melihat ini:
fn main() { println!("Size of a char: {}", std::mem::size_of::<char>()); // 4 bytes println!("Size of string containing 'a': {}", "a".len()); // .len() memberikan ukuran string dalam satuan byte println!("Size of string containing 'Γ': {}", "Γ".len()); println!("Size of string containing 'ε½': {}", "ε½".len()); println!("Size of string containing 'π ±': {}", "π ±".len()); }
Program ini akan mencetak:
Size of a char: 4
Size of string containing 'a': 1
Size of string containing 'Γ': 2
Size of string containing 'ε½': 3
Size of string containing 'π
±': 4
Anda bisa melihat bahwa huruf a
memerlukan 1 byte, aksara Jerman Γ
memerlukan 2 byte, aksara Jepang ε½
memerlukan 3 byte, dan aksara Mesir kuno π
±
memerlukan 4 byte.
fn main() { let slice = "Hello!"; println!("Slice is {} bytes.", slice.len()); let slice2 = "μλ !"; // Korean for "hi" println!("Slice2 is {} bytes.", slice2.len()); }
Program ini akan mencetak:
Slice is 6 bytes.
Slice2 is 7 bytes.
slice
memiliki panjang 6 karakter dan memerlukan 6 byte, namun slice2
memiliki panjang 3 karakter dan memerlukan 7 byte.
Jika .len()
memberikan informasi tentang size dalam satuan byte, bagaimana tentang ukuran dalam satuan panjang karakter? Kita akan mempelajari tentang method ini nanti, tapi Anda bisa mengingat bahwa method .chars().count()
bisa digunakan untuk keperluan tersebut. .chars().count()
merubah apa yang kita tulis menjadi karakter dan menghitung berapa banyak karakter yang terdapat dalam tulisan tersebut.
fn main() { let slice = "Hello!"; println!("Slice is {} bytes and also {} characters.", slice.len(), slice.chars().count()); let slice2 = "μλ !"; println!("Slice2 is {} bytes but only {} characters.", slice2.len(), slice2.chars().count()); }
Program ini akan mencetak:
Slice is 6 bytes and also 6 characters.
Slice2 is 7 bytes but only 3 characters.