Quick guide to Rust's From/Into and TryFrom/TryInto Traits
While working with Rust, I find myself implementing a lot of From/Into or TryFrom/TryInto traits in my code. Whether you're dealing with simple conversions or handling more complex scenarios with potential errors, mastering these traits is key to writing elegant Rust code.
From and Into Traits
We use the From trait to convert another type to our type. When we implement the From trait for our type, Rust automatically provides an implementation of the Into trait to convert our type to that another type.
Let's consider a scenario where we have a Car
struct with a property called brand
, which is of type String
. Now, suppose we have a variable brand_1: String
, and we want to convert it to an instance of Car
. We need to implement the From trait for the Car
struct.
#[derive(Debug)]
#[allow(dead_code)]
struct Car {
brand: String
}
impl From<String> for Car {
fn from(brand: String) -> Self {
Car { brand }
}
}
fn main() {
let brand_1 = String::from("FORD");
let car_1 = Car::from(brand_1);
println!("car_1: {:?}", car_1); // car_1: Car { Brand: "FORD" }
}
if we want to convert a Car
into a String
type, it might be natural to assume that we can achieve this by using the Into trait. However, in Rust, it is not the recommended approach, as the Rust documentation states:
One should avoid implementing
Into
and implementFrom
instead. ImplementingFrom
automatically provides one with an implementation ofInto
thanks to the blanket implementation in the standard library.
Therefore, following this guideline, we will implement From
trait for the String
type. This will enable us to invoke into
on a variable of String
type to convert it to a Car
instance.
#[derive(Debug)]
#[allow(dead_code)]
struct Car {
brand: String
}
impl From<String> for Car {
fn from(brand: String) -> Self {
Car { brand }
}
}
impl From<Car> for String {
fn from(car: Car) -> Self {
String::from(car.brand)
}
}
fn main() {
let brand_1 = String::from("FORD");
let car_1 = Car::from(brand_1);
println!("car_1: {:?}", car_1); // car_1: Car { brand: "FORD" }
let car_1_brand: String = car_1.into();
println!("car_1_brand: {:?}", car_1_brand); // car_1_brand: "FORD"
}
TryFrom and TryInto Traits
The TryFrom and TryInto traits are similar to the From and Into traits, but they handle conversions with the consideration of potential errors.
In our case, let's say we want to ensure that our Car
struct does not accept an empty brand name.
#[derive(Debug)]
#[allow(dead_code)]
struct Car {
brand: String
}
impl TryFrom<String> for Car {
type Error = &'static str;
fn try_from(brand: String) -> Result<Self, Self::Error> {
if brand.len() < 1 {
Err("Invalid brand name")
} else {
Ok(Car { brand })
}
}
}
fn main() {
let brand_1 = String::from("FORD");
let car_1 = Car::try_from(brand_1).unwrap();
println!("car_1: {:?}", car_1); // Car { brand: "FORD" }
let car_2 = Car::try_from(String::from("")).unwrap_err();
println!("car_2: {:?}", car_2); // "Invalid brand name"
}
Now we want to allow the conversion of our Car
struct to a String
, but not for certain specific brands.
#[derive(Debug)]
#[allow(dead_code)]
struct Car {
brand: String
}
impl TryFrom<String> for Car {
type Error = &'static str;
fn try_from(brand: String) -> Result<Self, Self::Error> {
if brand.len() < 1 {
Err("Invalid brand name")
} else {
Ok(Car { brand })
}
}
}
impl TryFrom<Car> for String {
type Error = &'static str;
fn try_from(car: Car) -> Result<Self, Self::Error> {
if car.brand == "KIA" || car.brand == "BMW" {
Err("Not allowed!")
} else {
Ok(String::from(car.brand))
}
}
}
fn main() {
let brand_1 = String::from("FORD");
let car_1 = Car::try_from(brand_1).unwrap();
println!("car_1: {:?}", car_1); // Car { brand: "FORD" }
let car_2 = Car::try_from(String::from("")).unwrap_err();
println!("car_2: {:?}", car_2); // "Invalid brand name"
let brand_1: Result<String, &'static str> = car_1.try_into();
println!("brand_1: {:?}", brand_1.unwrap()); // "FORD"
let car_3 = Car::try_from(String::from("KIA")).unwrap();
let brand_2: Result<String, &'static str> = car_3.try_into();
println!("brand_2: {:?}", brand_2.unwrap_err()); // "Not allowed!"
}
Conclusion
By now, you should feel confident in your ability to leverage these traits effectively, streamlining your code and improving its readability. Happy coding!