Question
I have a byte slice in Rust, and I want to take part of it and return that data as a fixed-size array.
For example:
fn pop(barry: &[u8]) -> [u8; 3] {
barry[0..3]
}
This does not compile because barry[0..3] produces a slice, not an array. Rust reports a type mismatch similar to:
expected array `[u8; 3]`, found slice `[u8]`
How can I convert a slice into a statically sized array like [u8; 3]?
Short Answer
By the end of this page, you will understand the difference between Rust slices and fixed-size arrays, why a slice cannot automatically become an array, and the main ways to convert or copy slice data into [u8; 3] safely and clearly.
Concept
In Rust, arrays and slices are related but different types.
- An array like
[u8; 3]has a size known at compile time. - A slice like
&[u8]is a dynamically sized view into some sequence of elements.
That is why this code fails:
fn pop(barry: &[u8]) -> [u8; 3] {
barry[0..3]
}
barry[0..3] is a slice expression, so its type is &[u8], not [u8; 3].
Rust keeps these types separate because they have different guarantees:
[u8; 3]always contains exactly 3 bytes&[u8]may contain any number of bytes
If you want a fixed-size array, Rust needs proof that exactly 3 elements are present. In practice, that usually means one of these approaches:
- Copy from the slice into a new array
- Try converting the slice into an array reference or array value
- Use pattern matching when the size is known structurally
This matters in real programming because many APIs use slices for flexibility, while lower-level logic, binary formats, and protocol parsing often need fixed-size arrays.
Mental Model
Think of a slice as a sticky note pointing at part of a row of boxes.
- The sticky note says: "start here, and there are some items"
- But it does not permanently own exactly 3 boxes
A fixed-size array is like a small tray with exactly 3 slots.
- The tray always has room for exactly 3 items
- Rust treats that as a different kind of object
So when you write barry[0..3], you are getting a sticky note pointing at 3 items, not a new 3-slot tray. If you want the tray, you must either:
- build it by copying those 3 items, or
- convert the slice only if Rust can verify the length is exactly 3
Syntax and Examples
The most common beginner-friendly solution is to copy the slice into an array.
fn pop(barry: &[u8]) -> [u8; 3] {
[barry[0], barry[1], barry[2]]
}
This works because you are explicitly building a new array.
A more scalable approach is to use try_into():
use std::convert::TryInto;
fn pop(barry: &[u8]) -> [u8; 3] {
barry[0..3].try_into().unwrap()
}
What this does
barry[0..3]creates a slice&[u8]try_into()attempts to convert that slice into[u8; 3]unwrap()assumes the conversion succeeds
This is valid because the slice length is exactly 3.
Step by Step Execution
Consider this example:
use std::convert::TryInto;
fn pop(barry: &[u8]) -> [u8; 3] {
barry[0..3].try_into().unwrap()
}
fn main() {
let data = [10, 20, 30, 40, 50];
let result = pop(&data);
println!("{:?}", result);
}
Step by step
-
datais created as:[10, 20, 30, 40, 50] -
pop(&data)passes a slice of the full array intopop.Inside , has type:
Real World Use Cases
Converting slices to fixed-size arrays is useful when working with structured binary data.
Common examples
- Network protocols: read exactly 4 bytes for an IPv4 address segment or header field
- File parsing: extract a fixed-size magic number from a file header
- Cryptography: convert chunks of bytes into fixed-size blocks or keys
- Embedded systems: read sensor packets with known byte lengths
- Image/audio formats: parse headers with exact byte counts
Example: reading a 3-byte RGB value from a byte buffer:
use std::convert::TryInto;
fn read_rgb(bytes: &[u8]) -> Option<[u8; 3]> {
bytes.get(0..3)?.try_into().ok()
}
This is especially common when external data arrives as slices, but your logic needs exact field sizes.
Real Codebase Usage
In real Rust codebases, developers usually prefer patterns that are both safe and expressive.
1. Guard clauses before slicing
Instead of assuming enough data exists, check first:
use std::convert::TryInto;
fn pop(barry: &[u8]) -> Option<[u8; 3]> {
if barry.len() < 3 {
return None;
}
barry[0..3].try_into().ok()
}
This avoids panics.
2. Using get() instead of direct indexing
use std::convert::TryInto;
fn pop(barry: &[u8]) -> Option<[u8; 3]> {
barry.get(0..3)?.try_into().ok()
}
This is a common production pattern because returns instead of panicking.
Common Mistakes
1. Expecting a slice expression to produce an array
Broken code:
fn pop(barry: &[u8]) -> [u8; 3] {
barry[0..3]
}
Why it fails:
barry[0..3]is a slice, not an array
Fix:
use std::convert::TryInto;
fn pop(barry: &[u8]) -> [u8; 3] {
barry[0..3].try_into().unwrap()
}
2. Indexing without checking length
Broken code:
fn pop(barry: &[u8]) -> [u8; 3] {
[barry[0], barry[1], barry[2]]
}
Why it is risky:
Comparisons
| Approach | Returns | Panics on short input? | Copies data? | Best for |
|---|---|---|---|---|
barry[0..3] | &[u8] | Yes | No | Borrowing a slice |
[barry[0], barry[1], barry[2]] | [u8; 3] | Yes | Yes | Very small manual arrays |
barry[0..3].try_into().unwrap() | [u8; 3] | Yes | Yes | Quick conversions when length is guaranteed |
barry.get(0..3)?.try_into().ok() |
Cheat Sheet
// Slice from array
let s: &[u8] = &data[0..3];
// Copy slice into fixed-size array
use std::convert::TryInto;
let a: [u8; 3] = data[0..3].try_into().unwrap();
// Safe version
let a: Option<[u8; 3]> = data.get(0..3)?.try_into().ok();
// Borrow fixed-size array instead of copying
let a_ref: Option<&[u8; 3]> = data.get(0..3)?.try_into().ok();
Rules to remember
arr[a..b]gives a slice, not an array[T; N]and are different types
FAQ
Why does barry[0..3] return a slice instead of an array?
Because Rust slice syntax creates a view into existing data. It does not create a new fixed-size array value.
How do I safely convert a slice to [u8; 3] in Rust?
Use:
use std::convert::TryInto;
bytes.get(0..3)?.try_into().ok()
This avoids panics and returns Option<[u8; 3]>.
Should I return [u8; 3] or &[u8; 3]?
Return [u8; 3] if you need ownership of the data. Return &[u8; 3] if borrowing is enough and you want to avoid copying.
Does converting a slice to an array copy the data?
Converting to [u8; 3] copies the 3 elements. Converting to &[u8; 3] borrows without copying.
Is unwrap() okay here?
Only if you know the input is always valid. For user input, file data, or network data, prefer or .
Mini Project
Description
Build a small Rust helper for parsing a packet prefix from raw bytes. Many binary formats begin with a fixed number of bytes that identify the message type or version. This project demonstrates how to safely extract a fixed-size array from a slice without panicking.
Goal
Create a function that reads the first 3 bytes from a byte slice and returns them safely as a [u8; 3].
Requirements
- Write a function that accepts
&[u8] - Return
Option<[u8; 3]> - Safely handle input shorter than 3 bytes
- Print the parsed prefix for valid input
- Print an error message for invalid input
Keep learning
Related questions
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.
Can a Struct Extend Another Struct in Rust? Composition vs Inheritance
Learn how Rust handles struct reuse without inheritance, using composition, traits, and wrapper structs with practical examples.