Chapter sebelumnya menutup dengan satu jaminan penting: konversi lewat From harus selalu berhasil. Kenyataannya, banyak konversi penting justru tidak bisa menjanjikan hal itu — i32 bernilai 300 tidak muat di u8 yang maksimal 255, teks "tiga puluh" tidak bisa menjadi angka, dan input mentah dari pengguna bisa berbentuk apa saja. Kebiasaan lama memakai casting as pada situasi seperti ini terasa praktis, tetapi as memotong nilai secara diam-diam tanpa memberi tahu kita. Standard library menyediakan pasangan trait TryFrom dan TryInto untuk konversi yang jujur: hasil konversinya adalah Result — Ok berisi nilai bila konversi berhasil, Err berisi error bila gagal — sehingga kegagalan tidak mungkin terlewat. Chapter ini menutup Section Tipe Lanjutan dengan membedah pasangan trait tersebut, dari konversi integer bawaan sampai tipe domain buatan sendiri.
Casting as yang Diam-diam Memotong #
Mari kita mulai dengan membuktikan bahaya as. Nilai 300 dikonversi menjadi u8 dengan dua cara sekaligus:
fn main() {
let value: i32 = 300;
let truncated = value as u8;
let checked = u8::try_from(value);
println!("Hasil as: {}", truncated);
match checked {
Ok(byte) => println!("Hasil try_from: {}", byte),
Err(_) => println!("Hasil try_from: konversi ditolak karena di luar rentang"),
}
}Output yang Dihasilkan:
Hasil as: 44
Hasil try_from: konversi ditolak karena di luar rentangMari kita bedah kode dan hasilnya:
- Baris 4 (
let truncated = value as u8;): castingasmemotong bit berlebih secara diam-diam. Nilai 300 dalam biner hanya muat 8 bit terbawahnya, sehingga hasilnya menjadi 300 - 256 = 44. Tidak ada error, tidak ada peringatan — angka salah langsung dipakai program. - Baris 5 (
let checked = u8::try_from(value);):TryFrommemeriksa rentang terlebih dahulu. Hasilnya bertipeResult<u8, TryFromIntError>— kegagalan menjadi nilai biasa yang bisa ditangani. - Baris 8-11 (
match checked { ... }): pola penanganan bakuResult. Karena 300 melebihi 255, jalurErryang tercetak, dan program tetap berjalan dengan keputusan yang kita tentukan sendiri.
TryFrom untuk Konversi Integer #
Trait TryFrom memiliki bentuk method try_from yang mengembalikan Result<Self, Self::Error> — nilai tujuan bila berhasil, error spesifik bila gagal. Untuk konversi antar tipe integer, error yang dipakai std adalah std::num::TryFromIntError. Pesan errornya membedakan dua penyebab kegagalan:
fn main() {
let inputs = [300, 7, -5];
for value in inputs {
match u8::try_from(value) {
Ok(byte) => println!("{} berhasil dikonversi menjadi u8: {}", value, byte),
Err(error) => println!("{} ditolak: {}", value, error),
}
}
}Output yang Dihasilkan:
300 ditolak: number too large to fit in target type
7 berhasil dikonversi menjadi u8: 7
-5 ditolak: number too small to fit in target typeMari kita bedah kode di atas:
- Baris 2 (
let inputs = [300, 7, -5];): tiga nilai uji — satu terlalu besar untuku8, satu valid, satu negatif (mustahil untuk tipe tanpa tanda). - Baris 5 (
match u8::try_from(value) {): pemanggilan bentuk path penuh, sama seperti polaEmailAddress::frompada Chapter From dan Into. Karena Rust edisi 2021 memasukkanTryFromke prelude, pemanggilan ini bekerja tanpauseapa pun. - Baris 7 (
Err(error) => println!("{} ditolak: {}", value, error),): error dicetak lewatDisplay. Pesan “number too large to fit in target type” keluar untuk 300, dan “number too small to fit in target type” keluar untuk -5 — dua penyebab kegagalan yang berbeda dari satu tipe error yang sama.
TryInto, Blanket Impl, dan Import Trait #
Sama seperti From yang mendonasikan Into, setiap impl TryFrom<T> for U otomatis memberi T method .try_into() melalui blanket impl TryInto di std; tipe error yang dikembalikan pun sama persis dengan tipe error milik TryFrom-nya. Satu perbedaan praktis: method seperti try_into hanya bisa dipanggil bila trait-nya berada dalam scope. Contoh berikut menuliskan import-nya secara eksplisit:
use std::convert::TryInto;
fn main() {
let raw: i32 = 130;
let converted: Result<u8, std::num::TryFromIntError> = raw.try_into();
match converted {
Ok(byte) => println!("{} masuk rentang u8: {}", raw, byte),
Err(error) => println!("{} ditolak: {}", raw, error),
}
}Output yang Dihasilkan:
130 masuk rentang u8: 130Tiga baris yang melakoni semuanya:
- Baris 1 (
use std::convert::TryInto;): membawa trait ke scope sehingga methodtry_intodapat dipanggil pada nilaii32. Penulisan eksplisit seperti ini sah dan sering dipakai agar niat kode terbaca jelas. - Baris 6 (
let converted: Result<u8, std::num::TryFromIntError> = raw.try_into();): bentuk sisi sebaliknya dariu8::try_from(raw). Karena 130 muat dalamu8, hasilnyaOk(130)— persis output yang tercetak. - Baris 8-11 (
match converted { ... }): hasil diperiksa eksplisit sebelum dipakai, sama seperti contohtry_fromsebelumnya.
Sebenarnya, pada Rust edisi 2021 import tersebut tidak lagi wajib: TryFrom dan TryInto sudah dimasukkan ke prelude edisi 2021. Berikut program yang sama tanpa baris use:
fn main() {
let raw: i32 = 130;
let converted: Result<u8, std::num::TryFromIntError> = raw.try_into();
match converted {
Ok(byte) => println!("{} masuk rentang u8: {}", raw, byte),
Err(error) => println!("{} ditolak: {}", raw, error),
}
}Dengan edisi 2021, program di atas dikompilasi bersih dan mencetak 130 masuk rentang u8: 130. Kondisi berbeda berlaku pada edisi 2018 — kompilasi dengan --edition 2018 gagal:
rustc --edition 2018 main.rs -o tryinto-demoOutput yang Dihasilkan:
error[E0599]: no method named `try_into` found for type `i32` in the current scope
--> main.rs:4:64
|
4 | let converted: Result<u8, std::num::TryFromIntError> = raw.try_into();
| ^^^^^^^^
|
--> /rustc/b940084d7eb6a299eb4bfeb8e34901bc051e7ac4/library/core/src/convert/mod.rs:625:7
|
= note: the method is available for `i32` here
|
= help: items from traits can only be used if the trait is in scope
= note: 'std::convert::TryInto' is included in the prelude starting in Edition 2021
help: trait `TryInto` which provides `try_into` is implemented but not in scope; perhaps you want to import it
|
1 + use std::convert::TryInto;
|
help: there is a method `into` with a similar name
|
4 - let converted: Result<u8, std::num::TryFromIntError> = raw.try_into();
4 + let converted: Result<u8, std::num::TryFromIntError> = raw.into();
|
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0599`.Pesan E0599 menjelaskan dirinya sendiri dengan lengkap: method try_into tersedia untuk i32, tetapi trait TryInto belum masuk scope; solusinya adalah menambahkan use std::convert::TryInto;; dan catatannya menyebutkan bahwa trait tersebut “included in the prelude starting in Edition 2021”. Pola “trait implemented but not in scope” juga muncul untuk trait lain seperti TryFrom, FromIterator, hingga trait crate pihak ketiga — bentuk pesannya layak dikenali.
TryFrom untuk Tipe Domain Kustom #
Kekuatan penuh TryFrom terlihat saat mendefinisikannya sendiri untuk tipe domain: constructor try_from boleh menolak input yang tidak sah, dan pesan errornya menjadi bagian dari kontrak tipe. Contoh berikut membuat Grade yang hanya menerima nilai 0 sampai 100:
use std::convert::TryFrom;
struct Grade(u8);
#[derive(Debug)]
struct GradeError(String);
impl TryFrom<i32> for Grade {
type Error = GradeError;
fn try_from(value: i32) -> Result<Grade, GradeError> {
if (0..=100).contains(&value) {
Ok(Grade(value as u8))
} else {
Err(GradeError(format!("nilai {} di luar rentang 0 sampai 100", value)))
}
}
}
fn main() {
let inputs = [85, 120, -3];
for value in inputs {
match Grade::try_from(value) {
Ok(grade) => println!("Nilai {} diterima: {}", value, grade.0),
Err(error) => println!("Nilai {} ditolak: {}", value, error.0),
}
}
}Output yang Dihasilkan:
Nilai 85 diterima: 85
Nilai 120 ditolak: nilai 120 di luar rentang 0 sampai 100
Nilai -3 ditolak: nilai -3 di luar rentang 0 sampai 100Mari kita bedah bagian pentingnya:
- Baris 5-6 (
#[derive(Debug)] struct GradeError(String);): error kustom berupa newtype — pola dari Chapter Newtype Pattern dipakai ulang di sini sebagai pembungkus pesan error. - Baris 8 (
impl TryFrom<i32> for Grade {): konversi darii32mentah menujuGradeyang terjamin sah. - Baris 9 (
type Error = GradeError;): associated type yang menentukan tipe kegagalan konversi — konsep yang dikenalkan pada Section Traits;try_fromakan mengembalikanResult<Grade, GradeError>. - Baris 11 (
fn try_from(value: i32) -> Result<Grade, GradeError>): signature kini memuat kemungkinan gagal secara eksplisit, berbeda dariFromyang mengembalikanGradelangsung. - Baris 12-16 (pemeriksaan rentang): nilai di luar 0-100 menghasilkan
Errberisi pesan Indonesia dariformat!; nilai valid diteruskan. Castingvalue as u8pada baris 14 aman karena pemeriksaan rentang baris 12 sudah memastikan nilainya 0 sampai 100 — bit tidak mungkin terpotong. - Baris 24-27 (
match Grade::try_from(value) { ... }): pemanggil membaca hasil lewatmatch, dan pesan error domain tercetak utuh pada output.
Kombinasi dengan Operator ? #
Result yang dikembalikan try_from kompatibel penuh dengan operator ? dari Section Penanganan Error — alat yang mem-forward error ke pemanggil tanpa boilerplate. Pola umumnya: parse teks menjadi angka, lalu validasi rentang, keduanya lewat ?:
fn parse_score(input: &str) -> Result<u8, Box<dyn std::error::Error>> {
let number: i32 = input.trim().parse()?;
let score = u8::try_from(number)?;
Ok(score)
}
fn main() {
for input in ["88", "300", "tiga puluh"] {
match parse_score(input) {
Ok(score) => println!("Skor dari '{}': {}", input, score),
Err(error) => println!("Input '{}' ditolak: {}", input, error),
}
}
}Output yang Dihasilkan:
Skor dari '88': 88
Input '300' ditolak: number too large to fit in target type
Input 'tiga puluh' ditolak: invalid digit found in stringMari kita bedah kode di atas:
- Baris 1 (
fn parse_score(input: &str) -> Result<u8, Box<dyn std::error::Error>>): tipe error fungsi adalahBox<dyn Error>— wadah error generik dari Section Smart Pointers yang sanggup menampung error apa pun. - Baris 2 (
let number: i32 = input.trim().parse()?;): parsing teks bisa gagal denganParseIntError(pesan “invalid digit found in string” pada output); operator?meneruskan error tersebut setelah mengonversinya keBox<dyn Error>lewat traitFrom— mekanisme yang dibahas Chapter sebelumnya bekerja diam-diam di sini. - Baris 3 (
let score = u8::try_from(number)?;): rentang angka diperiksaTryFrom; kegagalan seperti angka 300 meneruskanTryFromIntErrorke pemanggil dengan pesan “number too large to fit in target type”. - Dua sumber kegagalan, satu jalur: kedua kemungkinan gagal yang berbeda tipenya tergabung ke dalam satu
Resultberkat konversi otomatis?.
From Sebagai Kasus Khusus TryFrom #
Hubungan From dan TryFrom ditutup satu blanket impl terakhir di std: setiap impl From<T> for U otomatis juga menjadi impl TryFrom<T> for U dengan tipe error bernama Infallible — error yang tidak mungkin terjadi. Konversi lossless bisa dipanggil lewat try_from dan hasilnya selalu Ok:
use std::convert::TryFrom;
fn main() {
let converted: Result<String, std::convert::Infallible> = String::try_from("Halo");
println!("{:?}", converted);
}Output yang Dihasilkan:
Ok("Halo")String::from tidak pernah gagal, sehingga String::try_from("Halo") menghasilkan Ok("Halo") dengan tipe error Infallible yang tidak punya varian error sama sekali. Aturan pemilihannya menjadi tegas: implementasikan From bila konversi pasti berhasil, TryFrom bila ada nilai yang harus ditolak — dan keduanya sama-sama menyumbangkan versi method sebaliknya (into dan try_into) lewat blanket impl tanpa kerja tambahan.
Ringkasan #
TryFrom dan TryInto memodelkan konversi yang bisa gagal lewat Result: u8::try_from(300) menghasilkan Err(TryFromIntError) dengan pesan “number too large to fit in target type”, sementara casting as diam-diam memotong 300 menjadi 44. Blanket impl memberi method .try_into() pada semua tipe sumber; pada edisi 2018 method tersebut menuntut trait dalam scope (E0599 lengkap dengan solusi import), sedangkan edisi 2021 sudah memasukkan TryFrom dan TryInto ke prelude. Tipe domain mengikuti pola yang sama lewat type Error dan error kustom seperti GradeError, hasilnya kompatibel dengan operator ?, dan From otomatis menjadi TryFrom dengan error Infallible. Section Tipe Lanjutan kini lengkap; perjalanan berlanjut ke Section Desain API, tempat newtype, alias, marker, dan konversi dirangkai menjadi antarmuka library yang enak dipakai.