Question

Convert &str to &[u8] in Rust

ruststringslice

Question

How can a Rust &str be converted to a &[u8]?

For example, this code does not compile because f expects a byte slice:

fn f(s: &[u8]) {}

fn main() {
    let x = "a";
    f(x);
}

The compiler reports that it expected &[u8] but found &str. Rust documentation says that str has a direct mapping to byte slices, so how should this conversion be performed?

Short Answer

You will learn that &str and &[u8] both refer to UTF-8 bytes but are intentionally different Rust types. You can borrow the underlying bytes with as_bytes() without allocating or copying data.

Concept

A Rust &str is a borrowed string slice containing valid UTF-8. A &[u8] is a borrowed slice of arbitrary bytes.

Although a &str is represented internally as a view over UTF-8 bytes, Rust does not automatically treat it as &[u8]. The types carry different guarantees:

  • &str guarantees that its contents are valid UTF-8.
  • &[u8] makes no text-encoding guarantee; it may contain any byte values.

Use str::as_bytes to borrow a string's bytes:

fn f(bytes: &[u8]) {
    println!("{bytes:?}");
}

fn main() {
    let text = "hello";
    f(text.as_bytes());
}

as_bytes() is a zero-copy conversion: the returned byte slice points at the same data as the original &str.

Mental Model

Think of a &str as a package of bytes with a UTF-8 verified label attached.

A &[u8] is the same kind of package after removing that label: you can inspect each byte, but Rust no longer promises that the bytes form readable UTF-8 text.

as_bytes() does not repack the package. It simply lets you view the same data as individual bytes.

Take Quiz

Syntax and Examples

Use as_bytes() on a str or String reference.

fn print_bytes(bytes: &[u8]) {
    println!("{bytes:?}");
}

fn main() {
    let text: &str = "Rust";
    let bytes: &[u8] = text.as_bytes();

    print_bytes(bytes);
}

Output:

[82, 117, 115, 116]

For a String, use the same method:

fn main() {
    let name = String::from("Ada");
    let bytes: &[u8] = name.as_bytes();

    assert_eq!(bytes, &[65, 100, 97]);
}

bytes borrows from name, so name must remain alive while bytes is used.

UTF-8 characters may use more than one byte:

let text = "é";
assert_eq!(text.len(), 2);             // number of UTF-8 bytes
assert_eq!(text.as_bytes(), &[195, 169]);
assert_eq!(text.chars().count(), 1);   // number of Unicode scalar values

Step by Step Execution

Consider this program:

fn send(bytes: &[u8]) {
    println!("Sending {bytes:?}");
}

fn main() {
    let message = "OK";
    let raw_bytes = message.as_bytes();
    send(raw_bytes);
}

Step by step:

  1. message is a &str referring to the UTF-8 text "OK".
  2. message.as_bytes() creates a &[u8] view of that exact text data.
  3. ASCII O has byte value 79, and K has byte value 75.
  4. raw_bytes therefore refers to [79, 75].
  5. send receives the byte slice and prints it.

No Vec<u8>, heap allocation, or byte-by-byte copy is created.

Real World Use Cases

&[u8] is useful whenever an API operates on raw bytes rather than Rust text.

  • Writing to a network connection: protocols send bytes.
  • Hashing and cryptography: hash functions commonly accept byte slices.
  • Writing files: std::fs::write can write byte-oriented content.
  • Binary formats: headers, encoded messages, and checksums are byte data.
  • FFI and system APIs: lower-level interfaces often receive buffers of bytes.

Example: hashing text with a function that accepts bytes:

fn simple_sum(bytes: &[u8]) -> u32 {
    bytes.iter().map(|&byte| byte as u32).sum()
}

fn main() {
    let input = "abc";
    let result = simple_sum(input.as_bytes());

    assert_eq!(result, 294);
}
Take Quiz

Real Codebase Usage

In production Rust code, as_bytes() is typically used at a boundary where text becomes byte-oriented data.

Pass text to a byte API

fn write_packet(payload: &[u8]) {
    // Send or store payload.
}

fn send_username(username: &str) {
    write_packet(username.as_bytes());
}

Validate before parsing bytes

When data starts as bytes, validate it before treating it as text:

fn parse_header(bytes: &[u8]) -> Result<&str, std::str::Utf8Error> {
    std::str::from_utf8(bytes)
}

This is the reverse direction. from_utf8 checks the UTF-8 invariant required by &str.

Avoid unnecessary allocations

Do not create a Vec<u8> merely to call an API that accepts &[u8]:

let text = "event";
process(text.as_bytes()); // preferred

Use text.as_bytes().to_vec() only when you specifically need an owned, mutable, or independently stored byte buffer.

Common Mistakes

Passing &str where &[u8] is required

This fails because Rust does not perform this conversion implicitly:

fn f(_: &[u8]) {}

let text = "a";
f(text); // error: expected &[u8], found &str

Fix it with as_bytes():

f(text.as_bytes());

Assuming one character always equals one byte

This works for ASCII but not all Unicode text:

let text = "é";
assert_eq!(text.chars().count(), 1);
assert_eq!(text.as_bytes().len(), 2);

Use .chars() when working with Unicode scalar values, and .as_bytes() when working with UTF-8 encoding bytes.

Using as for the conversion

A cast is not the correct operation:

let text = "hello";
// let bytes = text as &[u8]; // does not compile

Use the explicit and safe API: text.as_bytes().

Converting bytes back to text without validation

Arbitrary bytes may not be valid UTF-8:

let bytes = [0xFF];
let text = std::str::from_utf8(&bytes);
assert!(text.is_err());

Use std::str::from_utf8 and handle its Result when converting &[u8] to &str.

Comparisons

Type or operationMeaningAllocation/copyUTF-8 guarantee
&strBorrowed UTF-8 textNoYes
&[u8]Borrowed arbitrary bytesNoNo
text.as_bytes()Views &str as bytesNoThe resulting type does not express it
std::str::from_utf8(bytes)Attempts to view bytes as textNoChecked at runtime
StringOwned, growable UTF-8 textMay allocate when createdYes
Vec<u8>Owned, growable byte bufferMay allocate when createdNo

The two main conversion directions are:

// &str -> &[u8]: always safe; UTF-8 is already known.
let bytes: &[u8] = "hello".as_bytes();

// &[u8] -> &str: must validate UTF-8.
let text: &str = std::str::from_utf8(b"hello")?;
Take Quiz

Cheat Sheet

// Borrow UTF-8 text as bytes.
let text: &str = "hello";
let bytes: &[u8] = text.as_bytes();

// Works for String too.
let name = String::from("hello");
let bytes: &[u8] = name.as_bytes();

// Convert bytes to text after UTF-8 validation.
let text: Result<&str, std::str::Utf8Error> = std::str::from_utf8(bytes);

// Create owned bytes only when ownership or mutation is needed.
let owned: Vec<u8> = text.as_bytes().to_vec();
  • as_bytes() borrows; it does not copy.
  • Byte length is text.len() or text.as_bytes().len().
  • Character count may differ from byte length for non-ASCII text.
  • A &[u8] does not guarantee valid UTF-8.
  • Use String::from_utf8 for an owned Vec<u8> that should become an owned String.

FAQ

Is as_bytes() a copy in Rust?

No. It returns a borrowed &[u8] pointing to the same UTF-8 data as the original &str.

Why does Rust not automatically convert &str to &[u8]?

They have different meanings and guarantees. Rust requires an explicit conversion so code clearly shows when it starts handling text as raw bytes.

How do I convert &[u8] back to &str?

Use std::str::from_utf8(bytes). It returns a Result because the bytes may not be valid UTF-8.

Does &str always use UTF-8?

Yes. Rust string slices are always valid UTF-8.

Can I modify the bytes returned by as_bytes()?

No. as_bytes() returns an immutable &[u8]. Modifying bytes could break the UTF-8 validity required by str.

Should I use .len() to count characters in a Rust string?

Not usually. str::len() counts bytes. Use .chars().count() when you need a count of Unicode scalar values.

How do I get an owned Vec<u8> from a string?

Use text.as_bytes().to_vec(). This copies the bytes into a new vector.

Take Quiz

Mini Project

Description

Build a small UTF-8 message inspector. It accepts text, passes it to a byte-oriented function, and reports both the byte values and the number of Unicode characters. This mirrors logging, protocol, and encoding-related utilities.

Goal

Convert a &str to &[u8] without copying and display the resulting UTF-8 bytes.

Requirements

Create a function that accepts a &[u8] and prints each byte. Accept text as a &str in main. Use as_bytes() to pass the text to the byte-processing function. Display both the byte length and Unicode character count. Use a non-ASCII example to show that bytes and characters can differ.

Take Quiz

Keep learning

Related questions

View more