Passing Slices to Functions
Functions that inspect or mutate contiguous sequences without taking ownership or growing the allocation should accept slice references (&[T], &mut [T], or &str) rather than references to specific container types (&Vec<T>, &[T; N], or &String).
Why &[T] and &str Beat &Vec<T> and &String
Through deref coercion (Vec<T>: Deref<Target = [T]> and String: Deref<Target = str>) and unsized coercion (&[T; N] -> &[T]), a single function taking &[T] or &str accepts every contiguous sequence representation in Rust without copying or allocating:
| Caller Argument Type | Passed to fn f(s: &[T]) |
Passed to fn f(v: &Vec<T>) |
|---|---|---|
&Vec<T> |
Works (via Deref coercion) |
Works |
&[T; N] (stack array) |
Works (via unsized coercion) | Compile error (unless heap-allocated into a Vec) |
&[T] (existing subslice) |
Works directly | Compile error |
&Box<[T]> / &Arc<[T]> |
Works (via Deref coercion) |
Compile error |
&'static [T] (static slice) |
Works directly | Compile error |
One Function Signature (`fn process(data: &[i32])`) Accepts All Contiguous Sources:
&Vec<i32> ──(Deref)────┐
&[i32; 4] ──(Unsize)───┼──> data: &[i32] (ptr + len)
&Box<[i32]> ──(Deref)────┤
&slice[1..3] ──(Direct)───┘
Accepting Shared Slices (&[T] and &str)
fn mean(samples: &[f64]) -> Option<f64> {
if samples.is_empty() {
return None;
}
let sum: f64 = samples.iter().sum();
Some(sum / samples.len() as f64)
}
fn count_vowels(text: &str) -> usize {
text.chars()
.filter(|c| matches!(c.to_ascii_lowercase(), 'a' | 'e' | 'i' | 'o' | 'u'))
.count()
}
fn main() {
let heap_vec: Vec<f64> = vec![10.0, 20.0, 30.0, 40.0];
let stack_arr: [f64; 3] = [5.0, 15.0, 25.0];
// Pass a `Vec<f64>`, a fixed array `[f64; 3]`, and a subslice `&[f64]`:
println!("Mean of Vec: {:?}", mean(&heap_vec));
println!("Mean of Array: {:?}", mean(&stack_arr));
println!("Mean of Subslice: {:?}", mean(&heap_vec[1..3]));
// Pass a `String`, a string literal `&'static str`, and a substring slice:
let owned_string = String::from("Rust Programming");
println!("Vowels in String: {}", count_vowels(&owned_string));
println!("Vowels in literal: {}", count_vowels("Systems"));
println!("Vowels in slice: {}", count_vowels(&owned_string[0..4]));
}
Mean of Vec: Some(25.0)
Mean of Array: Some(15.0)
Mean of Subslice: Some(25.0)
Vowels in String: 4
Vowels in literal: 1
Vowels in slice: 1
Accepting Mutable Slices (&mut [T]) for In-Place Algorithms
When a function modifies elements in place—such as sorting, reversing, normalizing, or zeroing a buffer—without inserting or removing elements, accept &mut [T]:
fn clamp_in_place(values: &mut [i32], min: i32, max: i32) {
for val in values {
*val = (*val).clamp(min, max);
}
}
fn main() {
let mut readings = vec![-10, 15, 42, 120, 88];
// Mutate only the middle 3 elements via a mutable subslice:
clamp_in_place(&mut readings[1..4], 0, 100);
println!("Middle clamped: {readings:?}");
// Mutate the entire vector via deref coercion `&mut Vec<i32>` -> `&mut [i32]`:
clamp_in_place(&mut readings, 0, 100);
println!("All clamped: {readings:?}");
}
Middle clamped: [-10, 15, 42, 100, 88]
All clamped: [0, 15, 42, 100, 88]
Generic Slice Conversions with AsRef<[T]> and AsRef<str>
If you want callers to pass arguments either by reference or by value (or accept wrapper types that convert cheaply to a slice, such as String, &str, Vec<u8>, or &[u8]), you can make the parameter generic over AsRef<[T]> or AsRef<str> while delegating immediately to a non-generic inner function taking &[T] or &str to avoid monomorphization bloat:
fn checksum(bytes: impl AsRef<[u8]>) -> u32 {
fn inner(slice: &[u8]) -> u32 {
slice.iter().map(|&b| u32::from(b)).sum()
}
inner(bytes.as_ref())
}
fn main() {
println!("From &str: {}", checksum("ABC"));
println!("From &[u8]: {}", checksum(&[65u8, 66, 67]));
println!("From Vec: {}", checksum(vec![65u8, 66, 67]));
}
From &str: 198
From &[u8]: 198
From Vec: 198