Help mutating a slice

Is it possible to write this function foo?

pub fn test() {
    let mut x = vec![0; 12];
    let mut y = x.as_mut_slice();

    fn foo(t: &mut [i32]) {
        todo!()}

    foo(&mut y);/* do equiv of y = &y[1..]; */
}

basically I want it to "advance" the slice by 1 element

Does &mut y[1..] not work?

1 Like

No, it doesn't.

In the XY problem, foo is a trait function, so it has to be done from within the function.

1 Like

If the slice wasn't mutable it would be easy.

pub fn main() {
    let x = vec![1,2,3,4];
    let mut y = x.as_slice();

    fn foo(t: &mut &[i32]) {
        *t = &t[1..];
    }

    println!("{:?}", y);
    foo(&mut y);
    println!("{:?}", y);
}

Could do a mutable slice with unsafe but maybe someone knows a different way.

2 Likes

Sure.

fn main() {
    let mut x = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
    let mut y = x.as_mut_slice();

    fn foo(t: &mut &mut [i32]) {
        *t = &mut std::mem::take(t)[1..]
    }

    foo(&mut y); /* do equiv of y = &mut y[1..]; */
    dbg!(y);
}
10 Likes

Does &mut &mut show up often? I don't recall needing it until now.

It's mainly come up for me when across struct boundaries (struct holds a &mut Something and you're in a &mut self method). I wouldn't say "often".

Here, you're really wanting to mutate the pointer to the slice/data (advance by one) and the number of elements in the slice (decrease by one), and both of those are held in the wide pointer to the slice (&mut [T]). So, &mut &mut [T] to mutate the &mut [T].

A simple iterator implementation for a slice reference (with no other supporting state) can be implemented in a similar way:

struct MyIterMut<'a, T>(&'a mut [T]);
impl<'a, T> Iterator for MyIterMut<'a, T> {
    type Item = &'a mut T;
    fn next(&mut self) -> Option<Self::Item> {
        match std::mem::take(&mut self.0) {
            [] => None,
            [one, rest @ ..] => {
                self.0 = rest;
                Some(one)
            }
        }
    }
}
3 Likes

Huh, so your answers made me do a double-take (heh), and TIL that &mut [] implements Default. So thanks for that!

1 Like

Heh. Yes, it's a special case useful for this use in particular. It would be sound for &mut to other ZSTs [1] too, as they can never actually alias memory.

In case it's useful to spell out for you or others, when you have a lifetime beneath a &mut, you can't change the inner lifetime:

//  vv 'a can be soundly shortened (is covariant)
   &'a mut &'b mut [T]
//          ^^ 'b cannot sound change at all (is invariant)

And you can't access the longer inner lifetime as such through the shorter outer lifetime:

fn foo<'a, 'b>(shared: &'a &'b [u8], uniq: &'a mut &'b mut [u8]) {
    // works because `&_` implements `Copy`
    let _: &'b [u8] = *shared;
    // this works because conceptually you copied out the inner reference
    // and then took the reference
    let _: &'b u8 = &shared[0];

    // fails because `&mut _` does *not* implement `Copy`
    // let _: &'b mut [u8] = *uniq;
    
    // fails because your access is capped to the shortest lifetime in the
    // access path (nothing could be copied)
    // let _: &'b u8 = &uniq[0];
}

So to get ahold of the &'b mut [_] (so you can return the appropriate item type [2], say), you need to get it out from behind the outer &mut somehow. &mut [] being default allows you to do so with mem::take.


  1. zero-sized types ↩︎

  2. with the longer lifetime ↩︎

2 Likes

Oh, I was unclear. My surprise was that &mut [T] had a default. But of course, that's because [] is a ZST and you can reasonably do &mut [] to instantiate a slice fat pointer. This means that we could technically even implement Default for any trait fat pointer for an object-safe trait that's defined for (), for example, which is interesting (not that I'm saying that we should).

Err, mea culpa, [] (the type) isn't a ZST, it's a DST; [] (the value) just happens to not alias anything. But the non-aliasing reasoning about ZSTs stands.


You can, or for anything you store statically (or promote) really. But for the &mut case, you need unsafe as no references to ZSTs in std are Default, and the &mut which are ([T], str) are not Sized and thus cannot be coerced into a dyn Trait.

For the boxed/owned case, any implementer that's also Default will do. And, come to think of it, if you have a constructable ZST that implements the trait in question, you might as well just return the Box<dyn Trait> version, as that won't actually allocate for ZSTs. Then the caller can make any &dyn Trait or &mut dyn Trait as they wish (though they won't be &'static _).

impl Default for Box<dyn MyTrait> {
    fn default() -> Self {
        Box::new(())
    }
}

Probably easiest to just call it the empty slice :wink:

"&mut [T] implements Default because we can use the empty slice"

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.