Rust String Conversions
Text conversions change text from one shape to another. The right name follows from the shape the program needs next.
Four roads from &str to String
All four copy the text and give you an owned String. They differ in where they come from:
let a = "hi".to_string();
let b = String::from("hi");
let owned_slice: String = "hello world"[0..5].to_owned();
let c: String = "hi".into();
println!("{} {} {} {}", a, b, owned_slice, c);to_owned copies any borrowed value
to_owned is not a text tool at all. It copies a borrowed slice of numbers into a Vec just as happily:
let nums: Vec<i32> = [1, 2, 3][..].to_owned();
println!("nums = {:?}", nums);Back to views and bytes
as_str and as_bytes lend. into_bytes gives the bytes away, and the String is gone:
let s = String::from("Ferris");
let view: &str = s.as_str();
println!("view = {} bytes0 = {}", view, s.as_bytes()[0]);
let bytes = s.into_bytes();
println!("into bytes = {:?}", bytes);From bytes: checked, or lossy
from_utf8 refuses bad bytes. from_utf8_lossy repairs them with � — and borrows the bytes untouched when they were already clean:
println!("from_utf8 = {:?}", String::from_utf8(vec![82, 117, 115, 116]));
println!("bad is err = {}", String::from_utf8(vec![255]).is_err());
let lossy = String::from_utf8_lossy(&[104, 105, 0xFF]);
println!("lossy = [{}]", lossy);Build a String from chars with collect
An iterator of chars collects into a String — transform and filter as you go:
let shout: String = "hey you".chars().filter(|c| *c != ' ').collect();
println!("collected = {}", shout);repeat copies text
repeat(n) builds a String of n copies — and zero copies is an empty String:
println!("repeat = [{}]", "ab".repeat(3));
println!("repeat0 = [{}]", "=".repeat(0));Example
fn main() {
let name: String = "ferris".into();
let shout = name.to_uppercase();
let bytes = shout.into_bytes();
println!("bytes = {:?}", bytes);
let back = String::from_utf8(bytes).unwrap();
println!("back = {}", back);
let fixed = String::from_utf8_lossy(&[104, 105, 0xFF]);
println!("fixed = {}", fixed);
let stars = "*".repeat(5);
println!("stars = {}", stars);
}Key points
- A conversion changes the shape of text, never its letters: view, owned text, and bytes can hold the same words.
- Every road from &str to String copies the text onto the heap.
- There is no free conversion from &str to String.
- Pick by reading, not by cost.
- to_string works on any Display value: numbers and your own types too, not just text
- to_owned needs no Display at all.
- to_owned copies any borrowed value into its owned partner.
- So &str becomes String, and &[T] becomes Vec<T>.
- to_owned is the only one of the four that works on a plain text slice.
- The slice must be cut from a bigger String.
- into() runs the From conversion from the other direction.
- The target type must be known from the context.
- A let annotation is one way to give the target type.
- Going back is free.
- as_str() lends the &str view of a String.
- as_bytes() lends its bytes.
- into_bytes() hands the bytes over as a Vec<u8> and ends the String.
- Bytes become text only through a check.
- String::from_utf8 returns a Result.
- String::from_utf8_lossy never fails: it swaps each bad run of bytes for the replacement character �.
In this example, one word travels the whole map. into() turns the literal "ferris" into an owned String. to_uppercase builds FERRIS, and into_bytes hands its bytes over as numbers: 70, 69, 82, 82, 73, 83. String::from_utf8 checks those bytes and turns them back into the text FERRIS. Then lossy conversion repairs a bad byte run — 104, 105 are "hi", and 0xFF becomes �. The last line builds a fresh String with repeat. Every arrow on the map, in one short program.
Note
FromStr is the parse trait for text that is read.
ToString and Display are for text that is written.
Implement Display and to_string comes free — never implement ToString by hand.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
You have a &str slice cut from a bigger String and need an owned String of just that slice. Which conversion fits best?