Loading page…
Rust walkthroughs
Loading page…
The itoa crate provides fast integer-to-string conversion. Unlike std::fmt::Display or ToString::to_string(), itoa avoids heap allocation and is significantly faster for integer formatting. It uses a lookup table and optimized algorithms to convert integers to strings directly into a buffer. This is especially useful in high-performance scenarios like serialization, logging, or any situation where you're converting many integers to strings.
Key concepts:
std::fmt::Write# Cargo.toml
[dependencies]
itoa = "1"use itoa::Buffer;
fn main() {
let mut buffer = Buffer::new();
// Convert integer to string
let s = buffer.format(12345);
println!("Number: {}", s);
// Works with all integer types
let s = buffer.format(-98765i32);
println!("Negative: {}", s);
let s = buffer.format(12345678901234567890u64);
println!("Large: {}", s);
}use itoa::Buffer;
fn main() {
let mut buffer = Buffer::new();
// Format different integer types
let positive = buffer.format(42);
println!("Positive: {}", positive);
let negative = buffer.format(-42);
println!("Negative: {}", negative);
// Different sizes
let tiny = buffer.format(0u8);
println!("Tiny: {}", tiny);
let large = buffer.format(i64::MAX);
println!("Large: {}", large);
let unsigned = buffer.format(u128::MAX);
println!("Unsigned: {}", unsigned);
}use itoa::Buffer;
fn main() {
// Buffer is reusable - allocate once
let mut buffer = Buffer::new();
for i in 0..10 {
// Same buffer used for each conversion
let s = buffer.format(i);
print!("{} ", s);
}
println!();
// The returned &str is only valid until next format() call
let s1 = buffer.format(1);
let s2 = buffer.format(2);
// s1 is now invalid - don't use it!
println!("s2: {}", s2);
// Store if you need the string longer
let s3 = buffer.format(3).to_string(); // Allocates
let s4 = buffer.format(4);
println!("s3: {}, s4: {}", s3, s4);
}use itoa::Buffer;
fn main() {
let mut buffer = Buffer::new();
// Signed integers
let i8_str = buffer.format(127i8);
println!("i8: {}", i8_str);
let i16_str = buffer.format(32767i16);
println!("i16: {}", i16_str);
let i32_str = buffer.format(2147483647i32);
println!("i32: {}", i32_str);
let i64_str = buffer.format(9223372036854775807i64);
println!("i64: {}", i64_str);
let i128_str = buffer.format(170141183460469231731687303715884105727i128);
println!("i128: {}", i128_str);
// Unsigned integers
let u8_str = buffer.format(255u8);
println!("u8: {}", u8_str);
let u16_str = buffer.format(65535u16);
println!("u16: {}", u16_str);
let u32_str = buffer.format(4294967295u32);
println!("u32: {}", u32_str);
let u64_str = buffer.format(18446744073709551615u64);
println!("u64: {}", u64_str);
let u128_str = buffer.format(340282366920938463463374607431768211455u128);
println!("u128: {}", u128_str);
// isize and usize
let isize_str = buffer.format(isize::MAX);
println!("isize: {}", isize_str);
let usize_str = buffer.format(usize::MAX);
println!("usize: {}", usize_str);
}use itoa::Buffer;
use std::time::Instant;
fn main() {
const ITERATIONS: u32 = 10_000_000;
let numbers: Vec<i32> = (0..1000).cycle().take(ITERATIONS as usize).collect();
// Using itoa
let mut buffer = Buffer::new();
let start = Instant::now();
for &n in &numbers {
let _s = buffer.format(n);
// Use _s for something to prevent optimization
std::hint::black_box(_s);
}
let itoa_time = start.elapsed();
// Using to_string()
let start = Instant::now();
for &n in &numbers {
let _s = n.to_string();
std::hint::black_box(_s);
}
let to_string_time = start.elapsed();
// Using format!
let start = Instant::now();
for &n in &numbers {
let _s = format!("{}", n);
std::hint::black_box(_s);
}
let format_time = start.elapsed();
println!("itoa: {:?}", itoa_time);
println!("to_string: {:?}", to_string_time);
println!("format!: {:?}", format_time);
println!("itoa speedup vs to_string: {:.2}x",
to_string_time.as_secs_f64() / itoa_time.as_secs_f64());
}use itoa::Buffer;
fn main() {
let mut buffer = Buffer::new();
// Build a string with multiple numbers
let mut result = String::new();
for i in 0..5 {
result.push_str(buffer.format(i));
if i < 4 {
result.push(',');
}
}
println!("Result: {}", result);
}use itoa::Buffer;
fn main() {
let mut buffer = Buffer::new();
let mut output: Vec<u8> = Vec::new();
// Write numbers as bytes
let numbers = [1, 20, 300, 4000, 50000];
for &n in &numbers {
let s = buffer.format(n);
output.extend_from_slice(s.as_bytes());
output.push(b' ');
}
// Convert to string for display
let result = String::from_utf8(output).unwrap();
println!("Output: {}", result);
}use itoa::Buffer;
fn format_json_array(numbers: &[i32]) -> String {
let mut buffer = Buffer::new();
let mut result = String::with_capacity(numbers.len() * 8);
result.push('[');
for (i, &n) in numbers.iter().enumerate() {
if i > 0 {
result.push(',');
}
result.push_str(buffer.format(n));
}
result.push(']');
result
}
fn main() {
let numbers = vec![1, 2, 3, 10, 100, 1000];
let json = format_json_array(&numbers);
println!("{}", json);
}use itoa::Buffer;
fn format_csv_row(values: &[i64]) -> String {
let mut buffer = Buffer::new();
let mut result = String::with_capacity(values.len() * 12);
for (i, &value) in values.iter().enumerate() {
if i > 0 {
result.push(',');
}
result.push_str(buffer.format(value));
}
result
}
fn main() {
let rows = [
vec![100, 200, 300],
vec![400, 500, 600],
vec![700, 800, 900],
];
for row in &rows {
println!("{}", format_csv_row(row));
}
}use itoa::Buffer;
fn format_with_prefix(n: i32, prefix: &str) -> String {
let mut buffer = Buffer::new();
let mut result = String::with_capacity(prefix.len() + 11);
result.push_str(prefix);
result.push_str(buffer.format(n));
result
}
fn format_with_suffix(n: u64, suffix: &str) -> String {
let mut buffer = Buffer::new();
let mut result = String::with_capacity(21 + suffix.len());
result.push_str(buffer.format(n));
result.push_str(suffix);
result
}
fn format_percentage(value: i32, total: i32) -> String {
let mut buffer = Buffer::new();
let percentage = (value * 100) / total;
let mut result = String::with_capacity(8);
result.push_str(buffer.format(percentage));
result.push('%');
result
}
fn main() {
println!("{}", format_with_prefix(42, "ID-"));
println!("{}", format_with_suffix(1024, "px"));
println!("{}", format_percentage(75, 100));
}use itoa::Buffer;
fn create_message(id: u32, data: &str) -> Vec<u8> {
let mut buffer = Buffer::new();
let mut message = Vec::with_capacity(32 + data.len());
// Message format: "id:length:data"
message.extend_from_slice(buffer.format(id).as_bytes());
message.push(b':');
message.extend_from_slice(buffer.format(data.len()).as_bytes());
message.push(b':');
message.extend_from_slice(data.as_bytes());
message
}
fn main() {
let msg = create_message(123, "Hello, World!");
println!("Message: {:?}", String::from_utf8_lossy(&msg));
// Parse example
let msg_str = String::from_utf8_lossy(&msg);
println!("Parsed: {}", msg_str);
}use itoa::Buffer;
fn main() {
let buffer = Buffer::new();
// Buffer is stack-allocated and sized for max integer
// u128::MAX is 39 digits, i128::MIN is 40 characters (including sign)
println!("Buffer size: {} bytes", std::mem::size_of::<Buffer>());
// You can also use heap-allocated buffers if needed
// But Buffer is efficient for most use cases
// Format the largest possible u128
let max_u128 = u128::MAX;
let s = buffer.format(max_u128);
println!("u128::MAX: {}", s);
println!("Length: {} characters", s.len());
}use itoa::{Buffer, Integer};
fn format_any_integer<I: Integer>(n: I) -> String {
let mut buffer = Buffer::new();
buffer.format(n).to_string()
}
fn main() {
// Works with any integer type
println!("i8: {}", format_any_integer(127i8));
println!("u16: {}", format_any_integer(65535u16));
println!("i32: {}", format_any_integer(-123456i32));
println!("u64: {}", format_any_integer(9876543210u64));
println!("i128: {}", format_any_integer(-999999999i128));
}use itoa::Buffer;
use std::io::{self, Write};
fn write_numbers<W: Write>(writer: &mut W, numbers: &[i32]) -> io::Result<()> {
let mut buffer = Buffer::new();
for (i, &n) in numbers.iter().enumerate() {
if i > 0 {
writer.write_all(b"\n")?;
}
writer.write_all(buffer.format(n).as_bytes())?;
}
Ok(())
}
fn main() -> io::Result<()> {
let numbers = vec![1, 2, 3, 4, 5];
let mut output = Vec::new();
write_numbers(&mut output, &numbers)?;
println!("{}", String::from_utf8_lossy(&output));
Ok(())
}use itoa::Buffer;
fn main() {
let mut buffer = Buffer::new();
// Count how many digits each number has
let numbers: Vec<i64> = vec![0, 9, 10, 99, 100, 999, 1000, -1, -10, -100];
for &n in &numbers {
let s = buffer.format(n);
let digits = if n < 0 { s.len() - 1 } else { s.len() };
println!("{} has {} digit(s)", n, digits);
}
}use itoa::Buffer;
use std::io::{self, Write};
struct LogWriter<W> {
writer: W,
buffer: Buffer,
}
impl<W: Write> LogWriter<W> {
fn new(writer: W) -> Self {
Self {
writer,
buffer: Buffer::new(),
}
}
fn log(&mut self, level: &str, code: u32, message: &str) -> io::Result<()> {
// Write timestamp
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
self.writer.write_all(b"[")?;
self.writer.write_all(self.buffer.format(timestamp).as_bytes())?;
self.writer.write_all(b"] ")?;
// Write level
self.writer.write_all(level.as_bytes())?;
self.writer.write_all(b": ")?;
// Write code
self.writer.write_all(b"[")?;
self.writer.write_all(self.buffer.format(code).as_bytes())?;
self.writer.write_all(b"] ")?;
// Write message
self.writer.write_all(message.as_bytes())?;
self.writer.write_all(b"\n")?;
Ok(())
}
}
fn main() -> io::Result<()> {
let mut logger = LogWriter::new(Vec::new());
logger.log("INFO", 200, "Request processed successfully")?;
logger.log("WARN", 429, "Rate limit exceeded")?;
logger.log("ERROR", 500, "Internal server error")?;
let output = String::from_utf8_lossy(&logger.writer);
print!("{}", output);
Ok(())
}use itoa::Buffer;
use std::io::{self, Write};
struct CsvWriter<W> {
writer: W,
buffer: Buffer,
first_field: bool,
}
impl<W: Write> CsvWriter<W> {
fn new(writer: W) -> Self {
Self {
writer,
buffer: Buffer::new(),
first_field: true,
}
}
fn field_i32(&mut self, value: i32) -> io::Result<()> {
self.write_separator()?;
self.writer.write_all(self.buffer.format(value).as_bytes())?;
Ok(())
}
fn field_u64(&mut self, value: u64) -> io::Result<()> {
self.write_separator()?;
self.writer.write_all(self.buffer.format(value).as_bytes())?;
Ok(())
}
fn field_str(&mut self, value: &str) -> io::Result<()> {
self.write_separator()?;
self.writer.write_all(value.as_bytes())?;
Ok(())
}
fn end_row(&mut self) -> io::Result<()> {
self.writer.write_all(b"\n")?;
self.first_field = true;
Ok(())
}
fn write_separator(&mut self) -> io::Result<()> {
if !self.first_field {
self.writer.write_all(b",")?;
}
self.first_field = false;
Ok(())
}
}
fn main() -> io::Result<()> {
let mut csv = CsvWriter::new(Vec::new());
// Header
csv.field_str("id")?;
csv.field_str("age")?;
csv.field_str("score")?;
csv.end_row()?;
// Data rows
csv.field_i32(1)?;
csv.field_i32(25)?;
csv.field_u64(950)?;
csv.end_row()?;
csv.field_i32(2)?;
csv.field_i32(30)?;
csv.field_u64(875)?;
csv.end_row()?;
csv.field_i32(3)?;
csv.field_i32(28)?;
csv.field_u64(920)?;
csv.end_row()?;
let output = String::from_utf8_lossy(&csv.writer);
print!("{}", output);
Ok(())
}use itoa::Buffer;
use std::io::{self, Write};
struct JsonNumberWriter<W> {
writer: W,
buffer: Buffer,
}
impl<W: Write> JsonNumberWriter<W> {
fn new(writer: W) -> Self {
Self {
writer,
buffer: Buffer::new(),
}
}
fn write_i32(&mut self, n: i32) -> io::Result<()> {
self.writer.write_all(self.buffer.format(n).as_bytes())
}
fn write_u64(&mut self, n: u64) -> io::Result<()> {
self.writer.write_all(self.buffer.format(n).as_bytes())
}
fn write_number_array(&mut self, numbers: &[u32]) -> io::Result<()> {
self.writer.write_all(b"[")?;
for (i, &n) in numbers.iter().enumerate() {
if i > 0 {
self.writer.write_all(b",")?;
}
self.writer.write_all(self.buffer.format(n).as_bytes())?;
}
self.writer.write_all(b"]")?;
Ok(())
}
fn write_key_value(&mut self, key: &str, value: i64) -> io::Result<()> {
self.writer.write_all(b"\"")?;
self.writer.write_all(key.as_bytes())?;
self.writer.write_all(b"\":")?;
self.writer.write_all(self.buffer.format(value).as_bytes())?;
Ok(())
}
}
fn main() -> io::Result<()> {
let mut json = JsonNumberWriter::new(Vec::new());
// Write object
json.writer.write_all(b"{")?;
json.write_key_value("count", 42)?;
json.writer.write_all(b",")?;
json.write_key_value("total", 123456789)?;
json.writer.write_all(b"}")?;
println!("{}", String::from_utf8_lossy(&json.writer));
// Write array
let mut json2 = JsonNumberWriter::new(Vec::new());
json2.write_number_array(&[1, 2, 3, 4, 5])?;
println!("{}", String::from_utf8_lossy(&json2.writer));
Ok(())
}use itoa::Buffer;
struct IdGenerator {
prefix: String,
counter: u64,
buffer: Buffer,
}
impl IdGenerator {
fn new(prefix: &str) -> Self {
Self {
prefix: prefix.to_string(),
counter: 0,
buffer: Buffer::new(),
}
}
fn next(&mut self) -> String {
self.counter += 1;
let mut result = String::with_capacity(self.prefix.len() + 20);
result.push_str(&self.prefix);
result.push_str(self.buffer.format(self.counter));
result
}
fn current(&self) -> u64 {
self.counter
}
}
fn main() {
let mut gen = IdGenerator::new("order-");
println!("ID: {}", gen.next());
println!("ID: {}", gen.next());
println!("ID: {}", gen.next());
println!("Generated {} IDs", gen.current());
}use itoa::Buffer;
use std::collections::BTreeMap;
struct Stats {
counts: BTreeMap<String, u64>,
buffer: Buffer,
}
impl Stats {
fn new() -> Self {
Self {
counts: BTreeMap::new(),
buffer: Buffer::new(),
}
}
fn increment(&mut self, key: &str) {
*self.counts.entry(key.to_string()).or_insert(0) += 1;
}
fn record(&mut self, key: &str, value: u64) {
self.counts.insert(key.to_string(), value);
}
fn format(&mut self) -> String {
let mut result = String::with_capacity(self.counts.len() * 20);
let mut first = true;
for (key, &value) in &self.counts {
if !first {
result.push('\n');
}
first = false;
result.push_str(key);
result.push(':');
result.push_str(self.buffer.format(value));
}
result
}
}
fn main() {
let mut stats = Stats::new();
stats.increment("requests");
stats.increment("requests");
stats.increment("requests");
stats.increment("errors");
stats.record("bytes_sent", 12345678);
stats.record("max_connections", 1000);
println!("{}", stats.format());
}use itoa::Buffer;
fn main() {
// Stack-allocated: Buffer is ~40 bytes
// No heap allocation for the formatted string
let mut buffer = Buffer::new();
for i in 0..1000 {
let s = buffer.format(i);
// s is a reference to the stack-allocated buffer
std::hint::black_box(s);
}
// Heap-allocated: Each to_string() allocates
// Higher memory pressure and allocation overhead
for i in 0..1000 {
let s = i.to_string();
// s owns a heap-allocated String
std::hint::black_box(s);
}
// When to use each:
// - Use itoa when you need speed and can reuse the buffer
// - Use to_string() when you need an owned String
// Demonstration of memory usage
println!("Buffer size: {} bytes", std::mem::size_of::<Buffer>());
println!("String size: {} bytes", std::mem::size_of::<String>());
// Buffer is reused, String allocates each time
println!("\nitoa: Reuse same stack memory");
println!("to_string: Allocate new heap memory each time");
}Buffer::new() to create a reusable stack-allocated bufferbuffer.format(integer) to get a &str representation&str is only valid until the next format() calli8, i16, i32, i64, i128, isize, and unsigned variantsto_string() or format!("{}") for integers.to_string() on the result