Quick guide to Rust's From/Into and TryFrom/TryInto Traits
Tags: rust
Published: 2023-07-29 09:06:50


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](https://doc.rust-lang.org/std/convert/trait.From.html) 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](https://doc.rust-lang.org/std/convert/trait.Into.html) 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.

```rust
#[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](https://doc.rust-lang.org/std/convert/trait.Into.html) states: 

> One should avoid implementing [`Into`](https://doc.rust-lang.org/std/convert/trait.Into.html "trait std::convert::Into") and implement [`From`](https://doc.rust-lang.org/std/convert/trait.From.html "trait std::convert::From") instead. Implementing [`From`](https://doc.rust-lang.org/std/convert/trait.From.html "trait std::convert::From") automatically provides one with an implementation of [`Into`](https://doc.rust-lang.org/std/convert/trait.Into.html "trait std::convert::Into") 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.
```rust
#[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](https://doc.rust-lang.org/std/convert/trait.TryFrom.html) and [TryInto](https://doc.rust-lang.org/std/convert/trait.TryInto.html) 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.
```rust
#[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.

```rust
#[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!