(Logo)

Mofafen Blog

Understanding into() in Rust

Published on March 23, 2025

Rust has several features that may seem unusual at first but serve an important purpose. One such feature is the into() method.

What Does into() Do?

into() is a method from the Into<T> trait that converts an object into a different type. Since Rust supports method overloading based on return type, the target conversion type is inferred from context.

For example:

// Conversion from int to float
let converted_value: f64 = 42.into();

According to the Rust documentation, into() performs a value-to-value conversion and is conceptually the opposite of From.

From<T> vs Into<U>

Why Implementing From<T> Automatically Provides Into<U>?

When implementing From<T> for U:

// Implementing From<T> for type U
trait From<T> {
    fn from(value: T) -> Self;
}

This provides a way to convert T into U. Rust then automatically provides an implementation of Into<U> for T:

// Into<T> for type U
trait Into<T> {
    fn into(self) -> T;
}

Rust offers a blanket implementation of Into in terms of From, meaning Into is always available when From is implemented:

impl<T, U> Into<U> for T
where
    U: From<T>,
{
    fn into(self) -> U {
        U::from(self)
    }
}

Example

Compiler Explorer link: here

struct Point {
    x: i32,
    y: i32,
}

impl From<i32> for Point {
    fn from(value: i32) -> Self {
        Point { x: value, y: value }
    }
}

fn main() {
    let num: i32 = 7;
    let p: Point = Point::from(num);
    let p2: Point = num.into(); // Automatically available
    println!("p = ({}, {})", p.x, p.y);
    println!("p2 = ({}, {})", p2.x, p2.y);
}

Difference Between From<T> and Into<U>

  • From<T> is explicitly implemented and defines how to convert T into a target type U.
  • Into<U> is automatically available when From<T> is implemented for U, allowing the use of .into().

When to Use .into() Over From::from()

1. Writing Generic Code

When writing generic code, the exact target type may not be known in advance. .into() offers more flexibility.

fn convert_to_f64<T: Into<f64>>(value: T) -> f64 {
    value.into() // Works with any type that implements Into<f64>
}

Counter-Example: Using From::from()

fn convert_to_f64<T>(value: T) -> f64 {
    f64::from(value) // Error: Rust cannot guarantee that f64::from(T) exists for all types
}

2. When the Target Type Can Be Inferred from Context

If the context already defines the target type, .into() keeps the code concise:

let num: i32 = 42;
let float_num: f64 = num.into(); // Rust infers the target type as f64

Counter-Example: Explicit Conversion

// Also works, but longer
let float_num2: f64 = f64::from(num);

However, if the target type is not known, .into() will not work:

let float_num = num.into(); // Error: target type cannot be inferred

3. Making Conversions More Flexible

When multiple types can be converted to a target type, .into() allows for a wider range of inputs.

struct Distance(f64);

impl From<i32> for Distance {
    fn from(value: i32) -> Self {
        Distance(value as f64)
    }
}

impl From<f32> for Distance {
    fn from(value: f32) -> Self {
        Distance(value as f64)
    }
}

fn print_distance<T: Into<Distance>>(value: T) {
    let d: Distance = value.into();
    println!("Distance: {}", d.0);
}

fn main() {
    print_distance(10); // Ok
    print_distance(5.5_f32); // Ok
}

When to Avoid .into()

You should not use .into() if you don’t need ownership. Instead, consider:

  • Borrowing (&T)
  • Using AsRef<T> (for read-only borrowed conversions)
  • Using .clone() or .to_owned() (to create a copy)
  • Implementing the Copy trait (for lightweight types)

Additionally, avoid .into() when the target type is unclear. In such cases, explicit conversion using From::from() or as is preferable.

Examples and Use Cases

Ownership with into()

Since Into<T> is a value-to-value conversion, the value is consumed. However, for primitive types implementing Copy, the value is not moved but copied:

let v_a: i32 = 43;
let v_b: f32 = v_a.into();
println!("v_a = {}", v_a); // Still valid
println!("v_b = {}", v_b);

For non-Copy types like String, the value is moved:

let v_a = String::from("hello");
let v_b: String = v_a.into();
// println!("v_a = {}", v_a); // Error: value has been moved
println!("v_b = {}", v_b);

Using .into() in Structs

A common pattern is accepting impl Into<String> parameters in constructors:

struct User {
    name: String,
    surname: String,
}

impl User {
    fn new(name: impl Into<String>, surname: impl Into<String>) -> Self {
        User { name: name.into(), surname: surname.into() }
    }
}

fn main() {
    // Conversion from `&str` to `String`
    let user1 = User::new("Albert", "Einstein");

    // Directly passing a `String`: no conversion
    let user2 = User::new(String::from("Satoshi"), String::from("Yamamoto"));
}

Summary

  • From<T> explicitly defines how to convert T into U.
  • Into<U> is automatically available when From<T> is implemented.
  • Use .into() when writing generic code, when the target type can be inferred, or to allow multiple input types.
  • Avoid .into() if ownership is not needed or when the target type is unclear.

References

  1. Rust Documentation
    Rust's standard library documentation on the Into trait.
    Link: Rust Documentation - Into Trait

  2. The Rust Programming Language - Chapter 10
    The official Rust book's chapter on conversion traits like From, Into, AsRef, and ToOwned.
    Link: The Rust Programming Language - Conversion Traits

  3. Rust by Example - Conversions
    Practical examples and code snippets on conversions between types in Rust.
    Link: Rust by Example - Into Trait

Rust

Comments

No comments yet. Be the first to comment!

Leave a Comment