Introduction: Why can't you just add a number to a string in Rust?
\n\nIf you come from Python or JavaScript, you're used to writing something like str(42) or 42 + "". That trick won't work in Rust. The compiler will strictly ask: "What type do you want? Integer? String? Make up your mind!"
The question "How do I convert from an integer to a string?" is one of the most popular on Stack Overflow in the Rust section. Thousands of developers scratch their heads wondering why their code doesn't compile when they try to write:
\n\nlet number = 42;\nlet text = number; // Error: expected `&str`, found `i32`\n\nToday we'll look under the hood of this conversion. You'll learn not only how to do it, but also how it works at the level of traits, allocations, and even assembly. Let's go!
\n\nThe Simplest Way: The to_string() Method
\n\nIn Rust, as in most modern languages, integer types have a to_string() method. It looks deceptively simple:
fn main() {\n let num: i32 = 255;\n let s: String = num.to_string();\n println!("{}", s); // Outputs: "255"\n}\n\nThis works for all integer types: i8, u32, i64, usize, etc.
What happens under the hood?
\n\nThe to_string() method is not compiler magic. It is implemented through the Display trait. In the Rust standard library, there is an implementation:
// Simplified version from the standard library\nimpl<T: fmt::Display> ToString for T {\n #[inline]\n fn to_string(&self) -> String {\n use fmt::Write;\n let mut buf = String::new();\n // Here the fmt() method of the Display trait is called\n let _ = buf.write_fmt(format_args!("{}", self));\n buf.shrink_to_fit(); // Memory optimization\n buf\n }\n}\n\nThe key point: to_string() creates a new String on the heap. Each call is a memory allocation. For hundreds of thousands of conversions, this can become a performance bottleneck.
Alternative: The format! Macro
\n\nThe second popular way is the format! macro. It works like println!, but instead of outputting to the console, it returns a String:
fn main() {\n let num = 1024;\n let s = format!("{}", num);\n println!("String length: {}", s.len()); // 4\n}\n\nThe difference between to_string() and format! is minimal. to_string() internally calls format_args! and write_fmt. Essentially, it's the same thing. The choice is a matter of style and readability.
When to use format!?
\n- \n
- When you need to insert a number in the middle of a string:
format!("Number: {}", num)\n - When the format is complex (hexadecimal, with separators):
format!("{:#x}", 255)→ "0xff" \n - When code expressiveness is important \n
Display vs ToString Trait: Which is Faster?
\n\nIf you are writing a library or high-performance code, use the Display trait directly via write! into a buffer. This allows you to reuse already allocated memory:
use std::fmt::Write;\n\nfn main() {\n let num = 42;\n let mut buffer = String::with_capacity(20); // Pre-allocate memory\n\n // Write the number into the buffer\n write!(buffer, "{}", num).unwrap();\n println!("{}", buffer); // "42"\n\n // Clear and reuse\n buffer.clear();\n write!(buffer, "{}", 256).unwrap();\n println!("{}", buffer); // "256"\n}\n\nThis approach provides up to 2-3x performance improvement in loops where you need to repeatedly convert numbers to strings, because memory allocation occurs