
Rust strings confused me when I first started, and they confuse almost everyone who comes from languages where a string is just a string. In Rust, &str and String are different in a fundamental way: &str is a borrowed, immutable view of a string slice with a fixed size, while String is an owned, heap-allocated value that can grow dynamically and be modified. Most of the time you’ll be working with String in Rust when re-allocating and moving values between structs, since you cannot hand a borrowed view around forever without thinking about lifetimes.
There are times you may need to convert a dynamic string to character bytes and static strings, and the variety of methods can feel overwhelming at first. Here are the ways to do it, organized by what you are converting from:
From &str
&str -> Stringhas many equally valid methods:String::from(st),st.to_string(),st.to_owned().- But I suggest you stick with one of them within a single project. The major advantage of
String::fromis that you can use it as an argument to amapmethod. So instead ofx.map(|s| String::from(s))you can often usex.map(String::from).
- But I suggest you stick with one of them within a single project. The major advantage of
&str->&[u8]is done byst.as_bytes()&str->Vec<u8>is a combination of&str -> &[u8] -> Vec<u8>, i.e.st.as_bytes().to_vec()orst.as_bytes().to_owned()
From String
String -> &strshould just be&swhere coercion is available ors.as_str()where it is not.String -> &[u8]is the same as&str -> &[u8]:s.as_bytes()String -> Vec<u8>has a custom method:s.into_bytes()
From &[u8]
&[u8] -> Vec<u8>is done byu.to_owned()oru.to_vec(). They do the same thing, butto_vechas the slight advantage of being unambiguous about the type it returns.&[u8] -> &strdoesn’t actually exist, that would be&[u8] -> Result<&str, Error>, provided viastr::from_utf8(u)str::from_utf8(u).unwrap()works, but you should prefer better error handling (see Error handling - The Result type).
&[u8] -> Stringis the combination of&[u8] -> Result<&str, Error> -> Result<String, Error>String::from_utf8(u.to_vec()).unwrap()works, but prefer better error handling (see Error handling - The Result type and alsoResult::map.
From Vec<u8>
Vec<u8> -> &[u8]should be just&vwhere coercion is available, orv.as_slice()where it’s not.Vec<u8> -> &stris the same asVec<u8> -> &[u8] -> Result<&str, Error>i.e.str::from_utf8(&v)str::from_utf8(&v).unwrap()works, but prefer better error handling (see Error handling - The Result type)
Vec<u8> -> Stringdoesn’t actually exist, that would beVec<u8> -> Result<String, Error>viaString::from_utf8(v)String::from_utf8(v).unwrap()works, but prefer better error handling (see Error handling - The Result type).
Coercion is available whenever the target is not generic but explicitly typed as &str or &[u8], respectively. The Rustonomicon has a chapter on coercions with more details about coercion sites.
tl;dr
&str -> String | String::from(s), s.to_string(), or s.to_owned()
&str -> &[u8] | s.as_bytes()
&str -> Vec<u8> | s.as_bytes().to_vec() or s.as_bytes().to_owned()
String -> &str | &s if possible* else s.as_str()
String -> &[u8] | s.as_bytes()
String -> Vec<u8> | s.into_bytes()
&[u8] -> Vec<u8> | s.to_vec() or s.to_owned()
&[u8] -> &str | str::from_utf8(s) -> Result<&str, _>; unwrap only if sure**
&[u8] -> String | String::from_utf8(s.to_vec()).unwrap(), but don't**
Vec<u8> -> &[u8] | &v if possible* else v.as_slice()
Vec<u8> -> &str | str::from_utf8(&v) -> Result<&str, _>; unwrap only if sure**
Vec<u8> -> String | String::from_utf8(v).unwrap(), but don't**
* target should have explicit type (i.e., checker can't infer that)
** handle the error properly instead
That is the cheat sheet I keep coming back to. Bookmark it, and the next time Rust hands you a byte slice when you wanted a string, you will know exactly which conversion to reach for.
