6

What does `&mut &[T]` mean?

 3 years ago
source link: https://ihatereality.space/04-what-mutref-to-slice-ref-means/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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)

(playground)

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]

(playground)

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
}

(playground)


Now you know what &mut &[T] means! I hope that was helpful. Bye.


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK