What does `&mut &[T]` mean?
References can be quite confusing, especially with different mutabilities. Indeed, only a third of people in my poll said that they understand what &mut &[T]
means!
Ground rules⌗
In Rust, to change a value you need to have some sort of unique access to it, for example, a &mut T
(a unique reference to T
). Shared references (&T
) on the other hand do not provide unique access on their own.
Since shared references don’t grant unique access, a shared reference to a unique reference &&mut T
is equivalent to &&T
in terms of mutating the value of type T
. So, if you have a shared reference in a type, you can’t mutate anything “after” it.
Slightly easier question: what does &mut &T
mean?⌗
So, if a shared reference doesn’t allow you to mutate anything “after” it, &mut &T
doesn’t allow you to mutate the value of type T
and thus is the same as &&T
or &T
? The former is true — you can’t mutate T
— but the latter is not true. While you can’t mutate the T
, you can mutate the reference &T
:
let value = 1;
let mut shared: &u32 = &value;
println!("{r:p}: {r} (value = {v})", r = shared, v = value);
// Prints <addr>: 1 (value = 1)
let unique: &mut &u32 = &mut shared;
*unique = &17;
println!("{r:p}: {r} (value = {v})", r = shared, v = value);
// Prints <different addr>: 17 (value = 1)
How is a slice different?⌗
With &mut &[T]
you can still change the reference, making it point to another slice. But &[T]
is essentially a fat pointer, which means that it’s not only a pointer but also a length of the slice.
Since you can mutate the reference, and the reference stores length as it’s part, you can change it too:
let mut slice: &[u8] = &[0, 1, 2, 3, 4];
let unique: &mut &[u8] = &mut slice;
// Since we want to hold unique reference,
// we can only access the slice through it
println!("({r:p}, {len}): {r:?}", r = *unique, len = unique.len());
// Prints (<addr>, 5): [0, 1, 2, 3, 4]
// Change only the length
*unique = &unique[..4];
println!("({r:p}, {len}): {r:?}", r = *unique, len = unique.len());
// Prints (<addr>, 4): [0, 1, 2, 3]
// Change both the pointer and the length
*unique = &unique[1..];
println!("({r:p}, {len}): {r:?}", r = *unique, len = unique.len());
// Prints (<addr+1>, 3): [1, 2, 3]
// Change only the pointer
*unique = &[17, 17, 42];
println!("({r:p}, {len}): {r:?}", r = *unique, len = unique.len());
// Prints (<different addr>, 3): [17, 17, 42]
One real-world example of &mut &[T]
may be the io::Read
implementation for &[u8]
:
use std::io::Read;
// We'll be reading *from* this slice
let mut data: &[u8] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
// And *into* this
let mut buf = [0; 3];
while let Ok(1..) = Read::read(&mut data, &mut buf) {
println!("({r:p}, {len}): {r:?}", r = data, len = data.len());
// This will print:
// (<addr>, 7): [3, 4, 5, 6, 7, 8, 9]
// (<addr+3>, 4): [6, 7, 8, 9]
// (<addr+6>, 1): [9]
// (<addr+7>, 0): []
// In reality you'd also examine the `buf` contents here
}
Now you know what &mut &[T]
means! I hope that was helpful. Bye.