Question
How can I convert a Rust string value into a vector of bytes?
A string literal can be converted at compile time with a macro in some contexts:
let bar = bytes!("some string");
However, this does not work when the text is stored in a variable because the macro expects a string literal:
let foo = "some string";
let bar = bytes!(foo);
What is the correct way to convert foo into a Vec<u8>?
Short Answer
Rust strings are UTF-8 text, so they can expose their encoded bytes as a &[u8] slice. Use .as_bytes().to_vec() when you need a new Vec<u8> while keeping the string, or .into_bytes() when you own a String and no longer need it.
Concept
Rust represents str and String text as valid UTF-8. UTF-8 stores text as one or more bytes per character, so Rust can provide the underlying encoded bytes.
There are two common goals:
- Borrow the bytes without allocating: use
as_bytes()and receive&[u8]. - Create an owned byte vector: use
as_bytes().to_vec()or, for an ownedString,into_bytes().
This distinction matters because Rust tracks ownership and borrowing. A byte slice borrows data owned by the string; a Vec<u8> owns a separate buffer (or, with into_bytes, takes over the string's existing buffer).
A String is text, not a generic collection of characters. Its bytes are UTF-8 encoded, which means an ASCII character such as A occupies one byte, while a character such as é may occupy multiple bytes.
Mental Model
Think of a String as a labeled package containing UTF-8 bytes.
as_bytes()lets you look at the package contents. You receive a borrowed view, so the original package must remain available.to_vec()makes a photocopy of the contents in a newVec<u8>.into_bytes()hands the package over and removes the text label. You now own its bytes, but cannot use the originalStringafterward.
The right choice depends on whether you need to keep the original string and whether another function needs ownership of the bytes.
Syntax and Examples
For a string slice (&str), borrow its bytes:
let text: &str = "some string";
let bytes: &[u8] = text.as_bytes();
println!("{bytes:?}");
// [115, 111, 109, 101, 32, 115, 116, 114, 105, 110, 103]
To create the Vec<u8> requested in the question, copy the slice:
let foo = "some string";
let bar: Vec<u8> = foo.as_bytes().to_vec();
println!("{bar:?}");
foo is a &str, foo.as_bytes() produces &[u8], and .to_vec() copies those bytes into an owned vector.
For an owned String, use into_bytes() when the string is no longer needed:
let foo = String::from("some string");
let bar: Vec<u8> = foo.into_bytes();
println!("{bar:?}");
into_bytes() consumes foo, so using foo after that line is a compile-time error.
Step by Step Execution
Consider this code:
fn main() {
let name = String::from("Ada");
let bytes = name.as_bytes().to_vec();
println!("{bytes:?}");
println!("{name}");
}
Execution steps:
String::from("Ada")creates an ownedStringcontaining UTF-8 text.name.as_bytes()borrowsnameand returns a byte slice:&[65, 100, 97]..to_vec()allocates an ownedVec<u8>and copies the three bytes into it.bytescontains[65, 100, 97].nameis still valid because it was only borrowed, not consumed.
If the second line were instead:
let bytes = name.into_bytes();
then the bytes are moved out of name. This avoids the copy, but name cannot be printed afterward.
Real World Use Cases
String-to-byte conversion is common when text must cross a byte-oriented boundary:
- Network requests: send text in a TCP or UDP payload.
- File writing: write a text value through APIs that accept
&[u8]. - Hashing and cryptography: hash the UTF-8 bytes of a message or password input.
- Database and protocol clients: serialize text fields into an encoded request body.
- Binary formats: include a UTF-8 label or identifier inside a binary packet.
For example, std::fs::write accepts anything implementing AsRef<[u8]>, so a byte slice is often enough:
use std::fs;
let message = "Saved from Rust";
fs::write("message.txt", message.as_bytes())?;
# Ok::<(), std::io::Error>(())
Do not create a Vec<u8> unless the receiving API requires ownership or you need to modify/store the bytes independently.
Real Codebase Usage
In production Rust, developers usually choose the least expensive representation that fits an API.
Pass a borrowed slice when possible
fn send(payload: &[u8]) {
// Send payload to a network, file, or encoder API.
}
let message = String::from("hello");
send(message.as_bytes());
This avoids allocating and copying a new vector.
Consume a temporary String when ownership is needed
fn queue(payload: Vec<u8>) {
// Store payload for later processing.
}
queue(format!("event:{}", 42).into_bytes());
The String produced by format! is temporary, so consuming it is efficient and clear.
Validate text before processing as bytes
When input must be restricted to ASCII, validate explicitly rather than assuming one character equals one byte:
fn require_ascii(value: &str) -> Result<&[u8], &'static str> {
if !value.is_ascii() {
return Err("only ASCII input is allowed");
}
Ok(value.as_bytes())
}
This guard clause makes protocol requirements visible and prevents incorrect byte-length assumptions.
Common Mistakes
Assuming every character is one byte
This is true for ASCII, but not all UTF-8 text:
let text = "é";
println!("characters: {}", text.chars().count()); // 1
println!("bytes: {}", text.len()); // 2
Use .chars() when working with Unicode characters and .as_bytes() only when working with encoded bytes.
Using into_bytes() and then using the String
let text = String::from("hello");
let bytes = text.into_bytes();
println!("{text}"); // Error: `text` was moved
Avoid this by borrowing and copying when you need both values:
let text = String::from("hello");
let bytes = text.as_bytes().to_vec();
println!("{text}");
Creating a Vec<u8> when a slice is enough
let bytes = text.as_bytes().to_vec();
process(&bytes);
If process accepts &[u8], write this instead:
process(text.as_bytes());
This avoids an allocation and copy.
Treating arbitrary bytes as valid UTF-8
A Vec<u8> can contain any byte values, but a Rust String must be valid UTF-8. Convert bytes back safely:
let bytes = vec![104, 105];
let text = String::from_utf8(bytes)?;
# Ok::<(), std::string::FromUtf8Error>(())
String::from_utf8 returns a Result because invalid byte sequences are possible.
Comparisons
| Need | Best choice | Allocation/copy | Can you keep the original String? |
|---|---|---|---|
| Temporarily read or pass bytes | text.as_bytes() | No | Yes |
| Own an independent byte vector | text.as_bytes().to_vec() | Yes | Yes |
Turn an owned String into bytes | text.into_bytes() | Usually no extra copy | No |
| Convert bytes back to text safely | String::from_utf8(bytes) | Depends | The vector is consumed |
&str versus String also affects what is available:
| Type | Meaning | Relevant conversion |
|---|---|---|
&str | Borrowed string slice, often a literal | as_bytes().to_vec() for Vec<u8> |
String | Owned, growable UTF-8 text | as_bytes(), as_bytes().to_vec(), or into_bytes() |
A compile-time macro that accepts literals is useful only when its input is known in source code. Methods such as as_bytes work with both runtime values and literals.
Cheat Sheet
// Borrow bytes from &str or String: no allocation
let slice: &[u8] = text.as_bytes();
// Make an owned Vec<u8>: copies bytes
let bytes: Vec<u8> = text.as_bytes().to_vec();
// Consume an owned String: use when `text` is no longer needed
let text = String::from("hello");
let bytes: Vec<u8> = text.into_bytes();
// Convert Vec<u8> back to String with UTF-8 validation
let text: String = String::from_utf8(bytes)?;
- Rust text is UTF-8.
&[u8]is borrowed;Vec<u8>is owned.String::len()reports bytes, not Unicode character count.into_bytes()moves/consumes aString.- Prefer
as_bytes()when an API only needs&[u8].
FAQ
How do I convert a &str to Vec<u8> in Rust?
Use text.as_bytes().to_vec():
let bytes = "hello".as_bytes().to_vec();
How do I convert a String to bytes without copying?
Use String::into_bytes():
let bytes = String::from("hello").into_bytes();
It consumes the String, so it cannot be used afterward.
What does as_bytes() return in Rust?
It returns &[u8], a borrowed slice of the string's UTF-8 encoded bytes. It does not allocate a vector.
Is String::len() the number of characters?
No. It is the number of UTF-8 bytes. Use text.chars().count() when you specifically need a Unicode scalar-value count.
Can every Vec<u8> become a String?
No. A Rust String must contain valid UTF-8. Use String::from_utf8(bytes), which returns a Result.
Should I use a byte macro for a runtime string variable?
No. Macros that require literals operate on source-code literals. For a variable, use methods such as as_bytes() and into_bytes().
Does as_bytes() work with non-English text?
Yes. It returns that text's UTF-8 encoding. Keep in mind that many characters require more than one byte.
Mini Project
Description
Build a small message encoder that accepts text and returns its UTF-8 bytes. The project demonstrates when to borrow bytes for immediate work and when to consume an owned String to create a payload for storage or transmission.
Goal
Encode a user message as Vec<u8> and display both its byte values and byte length.
Requirements
Create a function that accepts &str and returns a Vec<u8>.
Encode a message containing at least one non-ASCII character.
Print the original message and its byte vector.
Print the number of UTF-8 bytes.
Add a second example that uses String::into_bytes().
Keep learning
Related questions
Accepting Iterators in Rust Functions with impl Iterator
Learn how to write Rust functions that accept HashMap iterators using generic bounds or impl Iterator, avoiding trait object and lifetime errors.
Accessing Cargo Package Metadata in Rust
Learn how to read Cargo package metadata like version, name, and authors in Rust using compile-time environment macros.
Associated Types vs Generic Type Parameters in Rust: When to Use Each
Learn when to use associated types vs generic parameters in Rust traits, with clear rules, examples, and practical API design advice.