DEV Community

loading...

Type conversion in Rust (as, From, and Into)

Richard Oliver Bray
Full-time web developer. Part-time game developer. Content creator and novice photographer.
・4 min read

I'm fairly new to Rust and it's a language I've actually enjoyed learning, mainly for game development with Bevy. As someone who is coming for a JS/TS background, there is a huge learning curve and I'm eventually planning to create a video course on how to make a simple game with Rust, but for now, I'm going to document things down (in the form of dev.to articles) as and when I learn them to help people who are having difficulties.

This particular feature of Rust is something I've seen a lot in Bevy example code particularly when it comes to colours, for example:

materials.add(Color::rgb(0.7, 0.7, 0.7).into())
Enter fullscreen mode Exit fullscreen mode

The .into() method is something that I've scratched my brain over for ages and I've finally been able to figure out what it does. Well, at least I think I have.

as

Simple (explicit) type conversion or casting in Rust can be done with the as keyword:

fn main() {
  let num:u8 = 10;
  let multiplied = times_ten(num as f32);

  println!("{}", multiplied); // 100
}

fn times_ten(val: f32) -> f32 { 
  val * 10.0 
}
Enter fullscreen mode Exit fullscreen mode

From

From in Rust is one of two traits for type conversion (if you don't know what a trait I'm planning to write a post on it in the future).

Let's say I want to create my own special type called MySpecialNumber and want to convert an existing u32 variable to my custom type, I would do it like this:

#[derive(Debug)] // needed because struct displays nothing so needs to have a debug view
struct MySpecialNumber(u32);

impl From<u32> for MySpecialNumber {
  fn from(num: u32) -> Self {
    MySpecialNumber(num)
  }
}

fn main() {
  let existing_var = 30;
  let num = MySpecialNumber::from(existing_var);
  println!("{:?}", num); // Prints -> MySpecialNumber(30)
}
Enter fullscreen mode Exit fullscreen mode

If this is the first time you've seen the struct or impl keyword again, don't worry. I'm planning to cover those in future posts. They're a bit, different if you're coming from Javascript, you can think of them as a slightly more complicated way of making an object.

The above code snippet takes the existing_var of type u32 and changes it to type MySpecialNumber using the from method written above.

Because the From method is extending a trait it has to be written in a very specific way for it to work. If I changed the line fn from(num: u32) to fn from(num: f32) it wouldn't work. The source code for the From trait can be found on the Rust Github repo.

Into

Into is the second trait in Rust used for type conversion. It works in the opposite direction to the From trait, instead of creating x FROM y, it turns y INTO x, makes sense? It's a bit confusing right. I feel like code is the best way to explain things so let's jump straight into it (no pun intended).

First way to use Into

If there's an implementation of the From trait already we can also use the Into trait which is pretty cool.

To use into the main function of the above code snipped can be re-written as:

...

fn main() {
  let existing_var: u32 = 30;
  let num: MySpecialNumber = existing_var.into();
  println!("{:?}", num); // Prints -> MySpecialNumber(30)
}
Enter fullscreen mode Exit fullscreen mode

Arguably this was is more readable. The existing_var is being converted to MySpecialNumber because it has been added as a type annotation. If that get's removed Rust won't know what type to convert the existing variable to.

let num = existing_var.into(); // this won't work
Enter fullscreen mode Exit fullscreen mode

If you really wanted to, you could write a full Into method similar to the way I wrote the From method above using the Into that from the Rust source code, but I think From is good enough.

Second way to use Into

For this way we're going back to my times_ten function I used above when desiring the as keyword.

fn main() {
  let num:u8 = 10;
  let multiplied = times_ten(num as f32);

  println!("{}", multiplied); // 100
}

fn times_ten(val: f32) -> f32 { 
  val * 10.0 
}
Enter fullscreen mode Exit fullscreen mode

Instead of using as we can use the Into trait like so:

fn main() {
  let num:u8 = 10;
  let multiplied = times_ten(num);

  println!("{}", multiplied); // 100
}

fn times_ten<T: Into<f32>>(val: T) -> f32 { 
  val.into() * 10.0 
}
Enter fullscreen mode Exit fullscreen mode

So if you compare the last two code snippets, you'll see we can omit the as keyword and the type to convert to and move that logic to the times_ten function in the form of a generic data type T and the Into trait.

No matter what type the argument is that is passed into the times_ten function it will always be converted fo an f32 before it is multiplied by 10.

Don't worry if this doesn't make sense now, the more you use it, the more sense it will make.


Like I said at the beginning I'm new to Rust so there's a chance I haven't explained something properly or I've missed something out when it comes to type conversion then please let me know.

I've tried my best to explain this in the simplest way possible but if something I've said doesn't make any sense, again please let me know.

Sources

https://doc.rust-lang.org/rust-by-example/conversion/from_into.html
https://www.youtube.com/watch?v=vH5xXr81a2Y
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=c749b272a173bfb34371092c562551fa

Discussion (0)