Question
I want to convert a Vec<T> with length S into a fixed-size array of type [T; S] in Rust.
For example, I am using a function that returns a 128-bit hash as a Vec<u8>. That vector will always have length 16, and I would like to work with it as a [u8; 16] instead.
Is there a built-in way to do this, similar to as_slice(), or should I write my own function that creates a fixed-size array, copies the vector elements into it, and returns the array?
Short Answer
By the end of this page, you will understand how Rust handles Vec<T>, slices, and fixed-size arrays, and how to convert between them safely. You will also learn when conversion can fail, which standard-library methods to use, and which approach is best when you know the length should be exact.
Concept
In Rust, a Vec<T> and an array [T; N] are related but different types.
Vec<T>is a growable, heap-allocated collection.[T; N]is a fixed-size collection where the length is part of the type.
That difference is important:
- A
Vec<u8>can have any length at runtime. - A
[u8; 16]must have exactly 16 elements.
Because array length is part of the type, Rust cannot automatically assume that an arbitrary vector has the correct size. Even if you know the vector always contains 16 bytes, the compiler still needs a checked conversion.
This matters in real programs because fixed-size arrays are often used for:
- hashes like MD5 or UUID-like byte values
- cryptographic keys and nonces
- network protocol fields with known sizes
- binary parsing where exact byte counts matter
Rust's standard library provides built-in conversion support using TryFrom and try_into(). This is the idiomatic way to convert a slice or vector into an array when the length must match exactly.
If the length does not match, the conversion fails instead of silently truncating or padding. That makes the code safer and more explicit.
Mental Model
Think of a Vec<T> as a bag that can hold any number of items, while [T; N] is a tray with exactly N slots.
A vector might currently contain 16 items, but the type itself does not guarantee that. An array does guarantee it.
So converting a Vec<u8> to [u8; 16] is like checking whether your bag has exactly enough items to fill a 16-slot tray:
- If it has exactly 16 items, the transfer works.
- If it has 15 or 17 items, the transfer should fail.
Rust makes you perform that check explicitly.
Syntax and Examples
The most common modern Rust approach is to use try_into().
Converting a Vec<u8> to [u8; 16]
use std::convert::TryInto;
fn main() {
let bytes: Vec<u8> = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
let array: [u8; 16] = bytes.try_into().expect("expected exactly 16 bytes");
println!("{:?}", array);
}
What this does
bytes.try_into()attempts to convert the vector into a fixed-size array.- If the vector length is exactly 16, it succeeds.
- Otherwise, it returns an error.
expect(...)stops the program with a clear message if the length is wrong.
Converting from a slice
Sometimes you do not want to consume the vector. In that case, convert from a slice:
use std::convert::TryInto;
fn main() {
let bytes: Vec<u8> = vec![1; 16];
let array: [u8; 16] = bytes
.as_slice()
.try_into()
.expect("expected exactly 16 bytes");
println!("{:?}", array);
}
This copies the values from the slice into the array.
Returning a Result
If the input length might be wrong, returning a Result is often better than using expect.
use std::convert::TryInto;
fn to_hash(bytes: Vec<u8>) -> Result<[u8; 16], Vec<u8>> {
bytes.try_into()
}
fn main() {
let bytes = vec![0; 16];
match to_hash(bytes) {
Ok(hash) => println!("Hash: {:?}", hash),
Err(original_vec) => println!("Wrong length: {}", original_vec.len()),
}
}
This is useful when invalid input is possible and should be handled gracefully.
Step by Step Execution
Consider this example:
use std::convert::TryInto;
fn main() {
let bytes = vec![10, 20, 30, 40];
let array: [u8; 4] = bytes.try_into().expect("expected 4 bytes");
println!("{:?}", array);
}
Here is what happens step by step:
-
let bytes = vec![10, 20, 30, 40];- A
Vec<u8>is created on the heap. - Its length is 4.
- A
-
bytes.try_into()- Rust attempts to convert the vector into an array.
- The target type is known from the left side:
[u8; 4].
-
Length check
- Rust verifies that the vector has exactly 4 elements.
- Since it does, conversion succeeds.
-
expect("expected 4 bytes")- Because the conversion succeeded,
expectunwraps theOkvalue. - If conversion had failed, the program would panic with this message.
- Because the conversion succeeded,
-
println!("{:?}", array);- The array is printed.
Output:
[10, 20, 30, 40]
Now consider a failure case:
use std::convert::TryInto;
fn main() {
let bytes = vec![10, 20, 30];
let array: [u8; 4] = bytes.try_into().expect("expected 4 bytes");
println!("{:?}", array);
}
This fails because the vector length is 3, not 4.
Real World Use Cases
Fixed-size array conversion appears often in Rust code that deals with exact binary layouts.
Hashes and digests
A hashing function may produce 16 bytes for an MD5 digest or another fixed-length binary value.
use std::convert::TryInto;
fn parse_hash(bytes: Vec<u8>) -> Result<[u8; 16], Vec<u8>> {
bytes.try_into()
}
Cryptography
Keys, initialization vectors, and nonces often have exact sizes such as 16, 24, or 32 bytes.
use std::convert::TryInto;
fn load_key(bytes: Vec<u8>) -> Result<[u8; 32], Vec<u8>> {
bytes.try_into()
}
Binary protocols
A file header or packet field may be exactly 8 bytes.
use std::convert::TryInto;
fn read_header(bytes: &[u8]) -> Result<[u8; 8], std::array::TryFromSliceError> {
bytes.try_into()
}
Parsing identifiers
Some IDs are stored as exactly 16 bytes.
use std::convert::TryInto;
fn parse_id(bytes: &[u8]) -> Result<[u8; 16], std::array::TryFromSliceError> {
bytes.try_into()
}
In all these cases, using an array communicates an important rule: this value must always have exactly this length.
Real Codebase Usage
In real Rust projects, developers usually choose one of these patterns.
1. Convert as early as possible
If a value must be 16 bytes, convert it near the boundary of your program.
use std::convert::TryInto;
fn decode_hash(input: Vec<u8>) -> Result<[u8; 16], Vec<u8>> {
input.try_into()
}
This keeps the rest of the code strongly typed.
2. Use Result for validation
Instead of panicking, return an error when the length is wrong.
use std::convert::TryInto;
fn validate_token(bytes: &[u8]) -> Result<[u8; 16], std::array::TryFromSliceError> {
bytes.try_into()
}
This is common in APIs, parsers, and libraries.
3. Use guard clauses before conversion
Sometimes developers check the length first for clearer errors.
use std::convert::TryInto;
fn parse_block(bytes: Vec<u8>) -> Result<[u8; 16], String> {
if bytes.len() != 16 {
return Err(format!("expected 16 bytes, got {}", bytes.len()));
}
Ok(bytes.try_into().unwrap())
}
The guard clause makes the intent obvious.
4. Prefer arrays in domain types
If a hash is always 16 bytes, define functions and structs using [u8; 16] rather than Vec<u8>.
struct Hash128([u8; 16]);
This reduces bugs because invalid lengths become impossible after construction.
5. Convert from slices when borrowing
If you need to keep the original vector, borrow it as a slice and copy into an array.
use std::convert::TryInto;
fn first_16(bytes: &Vec<u8>) -> Result<[u8; 16], std::array::TryFromSliceError> {
bytes.as_slice().try_into()
}
This is useful when the vector is still needed elsewhere.
Common Mistakes
Here are common beginner mistakes when converting vectors to arrays in Rust.
Mistake 1: Assuming a Vec<T> is the same as [T; N]
Broken idea:
let v = vec![1, 2, 3, 4];
let a: [u8; 4] = v;
Why it fails:
Vec<u8>and[u8; 4]are different types.- Rust does not perform this conversion implicitly.
Fix:
use std::convert::TryInto;
let v = vec![1, 2, 3, 4];
let a: [u8; 4] = v.try_into().expect("expected 4 items");
Mistake 2: Ignoring the possibility of wrong length
Broken approach:
use std::convert::TryInto;
let v = vec![1, 2, 3];
let a: [u8; 4] = v.try_into().unwrap();
Why it is risky:
unwrap()will panic if the length is wrong.
Safer approach:
use std::convert::TryInto;
let v = vec![1, 2, 3];
let result: Result<[u8; 4], _> = v.try_into();
Mistake 3: Using the wrong array syntax
Broken code:
let hash: [u8, 16];
Why it fails:
- Rust array syntax is
[type; length], not[type, length].
Correct code:
let hash: [u8; 16];
Mistake 4: Expecting as_slice() to return an array
Broken assumption:
let slice = my_vec.as_slice(); // not a [u8; 16]
Why it matters:
as_slice()returns&[T], which is a slice, not a fixed-size array.
Fix:
use std::convert::TryInto;
let array: [u8; 16] = my_vec.as_slice().try_into().expect("expected 16 bytes");
Mistake 5: Writing manual copy code when standard conversion exists
Manual loops are usually unnecessary unless you need custom behavior.
Prefer:
use std::convert::TryInto;
let array: [u8; 16] = bytes.try_into().expect("expected 16 bytes");
This is clearer and more idiomatic.
Comparisons
Here is how the related Rust collection types compare.
| Type | Size known at compile time? | Growable? | Owns data? | Best use |
|---|---|---|---|---|
[T; N] | Yes | No | Yes | Exact fixed-size values |
Vec<T> | No | Yes | Yes | Dynamic collections |
&[T] | No | No | No | Borrowed view into contiguous data |
Vec<T> vs [T; N]
- Use
Vec<T>when the number of items can vary. - Use
[T; N]when the number of items is fixed and meaningful.
Slice to array vs vector to array
| Conversion | Consumes original value? | Copies elements? | Typical result type |
|---|---|---|---|
vec.try_into() | Yes | No extra copy of values | Result<[T; N], Vec<T>> |
slice.try_into() | No | Yes, into new array | Result<[T; N], TryFromSliceError> |
When to choose each
- Use
vec.try_into()when you no longer need the vector. - Use
as_slice().try_into()when you want to keep the vector. - Use arrays in APIs when fixed length is part of the meaning of the data.
Cheat Sheet
use std::convert::TryInto;
Convert Vec<T> to [T; N]
let v: Vec<u8> = vec![1, 2, 3, 4];
let a: [u8; 4] = v.try_into().expect("expected 4 items");
- Succeeds only if lengths match exactly.
- Consumes the vector.
Convert &[T] to [T; N]
let v: Vec<u8> = vec![1, 2, 3, 4];
let a: [u8; 4] = v.as_slice().try_into().expect("expected 4 items");
- Does not consume the vector.
- Copies values into a new array.
Result forms
let result: Result<[u8; 4], _> = v.try_into();
let result: Result<[u8; 4], _> = v.as_slice().try_into();
Remember
- Array syntax is
[T; N] Vec<T>is dynamic[T; N]is fixed-sizeas_slice()returns&[T], not[T; N]- Use
try_into(), not implicit conversion
Good default for exact-size data
fn parse_hash(bytes: Vec<u8>) -> Result<[u8; 16], Vec<u8>> {
bytes.try_into()
}
FAQ
Can I directly cast a Vec<u8> to [u8; 16] in Rust?
No. They are different types, so you need an explicit checked conversion such as try_into().
What is the idiomatic way to convert a Vec<T> to an array in Rust?
Use TryInto:
let arr: [u8; 16] = vec.try_into()?;
This works when the vector has exactly the right length.
What happens if the vector length is wrong?
The conversion returns an error. If you use unwrap() or expect(), the program will panic.
Should I use as_slice() first?
Use as_slice() if you want to keep the original vector. If you do not need the vector anymore, converting the vector directly is often better.
Does converting from a slice copy the data?
Yes. Converting from &[T] to [T; N] creates a new array value.
When should I use an array instead of a vector?
Use an array when the length is fixed and meaningful, such as hashes, keys, protocol fields, or small fixed binary values.
Is it worth writing a custom conversion function?
Usually not for simple conversions. The standard library already provides idiomatic checked conversion with try_into().
Mini Project
Description
Build a small Rust utility that accepts a byte vector and validates whether it represents a 16-byte hash. This demonstrates how to convert runtime-sized data into a strongly typed fixed-size array and reject invalid input safely.
Goal
Create a function that turns a Vec<u8> into a [u8; 16] and reports an error if the length is not exactly 16.
Requirements
- Write a function that takes a
Vec<u8>as input. - Return
Result<[u8; 16], String>from the function. - Reject input that is not exactly 16 bytes long.
- Print the valid hash if conversion succeeds.
- Print a helpful error message if conversion fails.
Keep learning
Related questions
Absolute Values in Rust: Using the abs() Method
Learn how to calculate absolute values in modern Rust with abs(), handle signed integer overflow, and choose safer alternatives.
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.
Access Enum Variant Values in Rust with Pattern Matching
Learn how to extract and print values stored in Rust enum variants using match and if let pattern matching, with practical examples.