Developers new to Rust probably have encountered the into and unwrap method calls several times in sample codes. At first sight, these calls may appear puzzling to them. So, where do we use these methods? Chances are these methods that are invoked from Option enums and structs that implement the Into (or From) trait.
Into Trait, Into Method
The Into trait is generally for data conversions. For instance, convert a struct instance into an object of a different struct definition. Consider the following sample codes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #[derive(Debug)] struct Person { name: String } impl Into<Employee> for Person { fn into(self) -> Employee { Employee { name: self.name } } } #[derive(Debug)] struct Employee { name: String } fn main() { let person: Person = Person { name: "Karl".to_string() }; let employee: Employee = person.into(); println!("{:?}", employee); } |
Rust Option and Unwrap Method
When we deal with the enum Option, we use the unwrap() method to get the value of the optional value. Consider the following codes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | // Get even numbers from a Vec instance fn get_even_numbers(v: Vec<i32>) -> Option<Vec<i32>> { let mut tmp_v = Vec::new(); for i in v.into_iter() { if i % 2 == 0 { tmp_v.push(i.clone()); } } if tmp_v.len() > 0 { // Return Some that has even numbers Some(tmp_v) } else { // Return None because there aren't any even numbers None } } // Main function fn main() { let vec_option1 = get_even_numbers(vec![1, 2, 4, 5]); println!("{:?}", vec_option1.unwrap()); let vec_option2 = get_even_numbers(vec![1, 3, 5, 7]); println!("{:?}", vec_option2.unwrap()); } |
The codes generate the following output.
1 2 3 4 5 | [2, 4] thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', src\main.rs:27:22 stack backtrace: 0: backtrace::backtrace::trace_unsynchronized ... |
The panic is caused by the unwrap() method when the Option has no value. To resolve this, we could use either of the following alternatives.
1 2 | println!("{:?}", vec_option2.unwrap_or(Vec::new())); println!("{:?}", vec_option1.unwrap_or_default()); |
Modifying the main function, we get the following codes.
1 2 3 4 5 6 7 8 9 10 11 12 13 | fn main() { let vec_option1 = get_even_numbers(vec![1, 2, 4, 5]); println!("{:?}", vec_option1.unwrap()); let vec_option2 = get_even_numbers(vec![1, 3, 5, 7]); //println!("{:?}", vec_option2.unwrap()); // Option 1 // println!("{:?}", vec_option2.unwrap_or(Vec::new())); // Option 2 println!("{:?}", vec_option2.unwrap_or_default()); } |
Then, the result will be a little different.
1 2 | [2, 4] [] |
If we look at the option.rs, we have these codes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | ... /// The `Option` type. See [the module level documentation](index.html) for more. #[derive(Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)] #[rustc_diagnostic_item = "option_type"] #[stable(feature = "rust1", since = "1.0.0")] pub enum Option<T> { /// No value #[stable(feature = "rust1", since = "1.0.0")] None, /// Some value `T` #[stable(feature = "rust1", since = "1.0.0")] Some(#[stable(feature = "rust1", since = "1.0.0")] T), } ... #[inline] #[track_caller] #[stable(feature = "rust1", since = "1.0.0")] pub fn unwrap(self) -> T { match self { Some(val) => val, None => panic!("called `Option::unwrap()` on a `None` value"), } } |
The unwrap() method simply returns the Option value, if there is any. Otherwise, it returns panics for None. What if we want our Option enum that works the same way?
First, we need to create our enum type.
1 2 3 4 | enum MyOption<T> { HasSome(T), HasNone, } |
Second, implement the Default trait for dealing with an empty value.
1 2 3 4 5 6 7 8 | impl<T: Default> MyOption<T> { pub fn unwrap(self) -> T { match self { MyOption::HasSome(x) => x, MyOption::HasNone => Default::default(), } } } |
Third, modify the get_even_numbers and use the new enum.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // Get even numbers from a Vec instance fn get_even_numbers(v: Vec<i32>) -> MyOption<Vec<i32>> { let mut tmp_v = Vec::new(); for i in v.into_iter() { if i % 2 == 0 { tmp_v.push(i.clone()); } } if tmp_v.len() > 0 { // Return Some that has even numbers MyOption::HasSome(tmp_v) } else { // Return None because there aren't any even numbers MyOption::HasNone } } |
Using the original main function, it generates the following output.
1 2 | [2, 4] [] |
In case the enums are in a separate module and are using the #[non_exhaustive], please consider reading Using #[non_exhaustive] for Non-exhaustive Enums.