How to accept both a Vec and a slice in one function
Short answer: Declare the parameter &[T]. A &Vec<T> and a &[T; N] both coerce to &[T]
at the call site, so one function takes a Vec, an array, a window into either, or a slice that
already exists — and nobody clones anything.
One signature, four call shapes
Section titled “One signature, four call shapes”In a fresh project (cargo new slice-param), replace src/main.rs with this and run cargo run:
fn sum(values: &[i32]) -> i32 { values.iter().sum()}
fn main() { let numbers = vec![1, 2, 3, 4]; let array = [10, 20, 30]; let slice: &[i32] = &numbers;
println!("{}", sum(&numbers)); println!("{}", sum(&array)); println!("{}", sum(&numbers[1..])); println!("{}", sum(slice));}1060910All four calls reach the same function. &numbers is a &Vec<i32> and &array is a
&[i32; 3]; where a &[i32] is expected, the compiler coerces both, because a Vec derefs to a
slice and an array is a slice with its length in the type. &numbers[1..] is already a slice — a
window from the second element on. slice is a &[i32] and needs nothing done to it. The
explicit spellings, &numbers[..] and numbers.as_slice(), produce the same type.
What &Vec<T> costs the caller
Section titled “What &Vec<T> costs the caller”Type the parameter &Vec<i32> and the array call stops compiling:
fn total(values: &Vec<i32>) -> i32 { values.iter().sum()}
fn main() { let numbers = [1, 2, 3]; println!("{}", total(&numbers));}error[E0308]: mismatched types --> src/main.rs:7:26 |7 | println!("{}", total(&numbers)); | ----- ^^^^^^^^ expected `&Vec<i32>`, found `&[{integer}; 3]` | | | arguments to this function are incorrect | = note: expected reference `&Vec<i32>` found reference `&[{integer}; 3]`note: function defined here --> src/main.rs:1:4 |1 | fn total(values: &Vec<i32>) -> i32 { | ^^^^^ -----------------
For more information about this error, try `rustc --explain E0308`.Worse, a caller holding a slice has to build a Vec to call you — an allocation and a copy of
every element, on every call:
fn total(values: &Vec<i32>) -> i32 { values.iter().sum()}
fn main() { let slice: &[i32] = &[1, 2, 3]; println!("{}", total(&slice.to_vec()));}6to_vec() is the clone the &[T] parameter removes. If the function only reads, &[T] is the
signature that costs callers the least.
Writing through it: &mut [T]
Section titled “Writing through it: &mut [T]”The same rule covers mutation. A &mut [i32] parameter accepts a &mut Vec<i32>, and the writes
land in the Vec’s own buffer:
fn double(values: &mut [i32]) { for value in values.iter_mut() { *value *= 2; }}
fn main() { let mut numbers = vec![1, 2, 3]; double(&mut numbers); println!("{numbers:?}");}[2, 4, 6]&mut numbers is reborrowed as a mutable view for the call — what reborrowing
is has the details — and the Vec is still usable
afterwards, because it never moved.
A generic parameter, fn sum<T: AsRef<[i32]>>(values: T), also accepts a Vec and an array, and
it takes an owned Vec as well. For a function that only reads, that is more signature than the
job needs: &[T] already accepts every caller above, and coercion does the work.
Related
Section titled “Related”- What is the difference between a Vec and a slice? — the ownership split behind this page
- How to pass a Vec to a function without cloning it — the same signature, from the ownership side
- What is the difference between String and &str? — the same split, for text
- What is reborrowing? — what
&mut numbersbecomes at the call
Next in this chapter: how to sort a Vec.