Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I modify a slice that is a function parameter?

Tags:

rust

Parameters can be passed to functions and modified:

fn set_42(int: &mut i32) {
    *int += 42;
}

fn main() {
    let mut int = 0;
    set_42(&mut int);
    println!("{:?}", int);
}

Output:

42

Changing the code to use a slice fails with a whole bunch of errors:

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

fn main() {
    let mut slice = &[0, 1, 2, 3][..];
    pop_front(&mut slice);
    println!("{:?}", slice);
}

Output:

error[E0308]: mismatched types
 --> src/main.rs:2:14
  |
2 |     *slice = &{ slice }[1..];
  |              ^^^^^^^^^^^^^^^
  |              |
  |              expected slice `[i32]`, found `&[i32]`
  |              help: consider removing the borrow: `{ slice }[1..]`

error[E0277]: the size for values of type `[i32]` cannot be known at compilation time
 --> src/main.rs:2:5
  |
2 |     *slice = &{ slice }[1..];
  |     ^^^^^^ doesn't have a size known at compile-time
  |
  = help: the trait `std::marker::Sized` is not implemented for `[i32]`
  = note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
  = note: the left-hand-side of an assignment must have a statically known size

If we try using a mutable slice (which isn't what I really want; I don't want to modify the values within the slice, I just want to modify the slice itself so it covers a smaller range of elements) and a mutable parameter, it has no effect on the original slice:

fn pop_front(mut slice: &mut [i32]) {
    slice = &mut {slice}[1..];
}

fn main() {
    let mut slice = &mut [0, 1, 2, 3][..];
    pop_front(&mut slice);
    println!("{:?}", slice);
}

Output:

[0, 1, 2, 3]

Is there a way to modify a slice that's a function parameter? I don't want to modify the elements within the slice; I just want to modify the range of the slice itself so it becomes a smaller "sub-slice".

like image 980
Cornstalks Avatar asked Dec 20 '15 18:12

Cornstalks


People also ask

How do you pass a slice to a function in Go?

Slices in Go have a base array and are passed by reference, not value. We just pass the slice reference to the function, then change it, and it changes the outer slice.

How do you pass a slice as an argument in Golang?

In Go language, you are allowed to pass a slice to a function, means the function gets the copy of the slice. Explanation: In the above example, we have a slice named as slc. This slice is passed in the myfun() function.

How do you add elements to a slice?

As we already know that slice is dynamic, so the new element can be appended to a slice with the help of append() function. This function appends the new element at the end of the slice.

Are slices passed by reference or value Golang?

Golang default is pass by Value. There is no pass by reference like in c++. Slices when its passed it's passed with the pointer to underlying array.


3 Answers

Using part of Francis Gagné's answer (I didn't think of trying &mut &), I was able to get it working without using unsafe code:

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

fn main() {
    let mut slice = &[0, 1, 2, 3][..];
    pop_front(&mut slice);
    println!("{:?}", slice);
}

Output:

[1, 2, 3]
like image 174
Cornstalks Avatar answered Oct 20 '22 01:10

Cornstalks


As others have said, the core idea here is to take a &mut &... [T] (where ... is mut or empty) and read/write to the internal slice. The other answers demonstrate it is possible for &mut &[T] in safe code, and possible for &mut &mut [T] with unsafe code, but they don't explain why there's the difference... and &mut &mut [T] is possible with safe code too.

In explicit-lifetime terms, the nested reference is something like &'a mut &'b ... [T] for some lifetimes 'a and 'b, and the goal here is to get a &'b ... [T], slice it and write that into the &'a mut.

For &'a mut &'b [T], this is easy: &[T] is copy, so writing *slice = &slice[1..] will effectively copy the &'b [T] out of the &mut and then, later, overwrite the existing value with the shorter one. The copy means that one literally gets a &'b [T] to operate with, and so there's no direct connection between that and the &'a mut, and hence it is legal to mutate. It is effectively something like

fn pop_front<'a, 'b>(slice: &'a mut &'b[i32]) {
    // *slice = &slice[1..] behaves like
    let value: &'b [i32] = *slice;
    *slice = &value[1..]
}

(I've labelled the lifetimes and annotated the type to tie into my explanation, but this is not required for the code to work.)

For &'a mut &'b mut [T] things are a little trickier: &mut [T] cannot be copied: dereferencing won't copy, it will reborrow to give a &'a mut [T] i.e. the slice has a lifetime that is connected to the outer &'a mut, not the inner &'b mut [T]. This means the sliced reference has a shorter lifetime than the type it is trying to overwrite, so it's invalid to store the slice into that position. In other words:

fn pop_front<'a, 'b>(slice: &'a mut &'b mut [i32]) {
    let value: &'a mut [i32] = &mut **slice;
    *slice = &mut value[1..] // error
}

The way to do this safely for &'a mut &'b mut [T] is to get the internal slice out of the reference with that 'b lifetime. This requires keeping track of the "one owner" rule, doing no borrowing, and the best function for this sort of ownership manipulation is mem::replace. It allows us to extract the inner &'b mut [T] by swapping it with some placeholder, which we can then overwrite with the short version. The best/only placeholder is an empty array: writing &mut [] can be a &'c mut [X] for any type X and any lifetime 'c, since there's no data to store and so nothing needs initialisation, and no data will ever become invalid. In particular, it can be a &'b mut [T]:

fn pop_front<'a, 'b>(slice: &'a mut &'b mut [i32]) {
    let value: &'b mut [i32] = mem::replace(slice, &mut []);
    *slice = &mut value[1..]
}

Since &mut[T] implements Default, we can also use mem::take:

fn pop_front<'a, 'b>(slice: &'a mut &'b mut [i32]) {
    let value: &'b mut [i32] = mem::take(slice);
    *slice = &mut value[1..]
}

(As above, I've made things more explicit than necessary.)

See also:

  • Why can't I assign one dereference of a reference of a reference to another when the outer lifetimes differ?
  • Why does linking lifetimes matter only with mutable references?
  • How can I swap in a new value for a field in a mutable reference to a structure?
like image 21
huon Avatar answered Oct 20 '22 00:10

huon


If you need to modify an immutable slice, see Cornstalks's answer.

You cannot modify a mutable slice in safe Rust. When you take a subslice of a mutable slice, you effectively borrow from the original slice. This means that the subslice must not outlive the original slice.

You want something that looks like this:

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

but the subslice slice[1..] is only valid until the end of the function, and which point the borrow will end and the original slice (the slice parameter) will be usable again.

We can use some unsafe code to construct manually the slice we want:

use std::slice;

fn pop_front(slice: &mut &mut [i32]) {
    let ptr = slice.as_mut_ptr();
    let len = slice.len();
    *slice = unsafe { slice::from_raw_parts_mut(ptr.offset(1), len - 1) };
}

fn main() {
    let mut slice = &mut [0, 1, 2, 3][..];
    pop_front(&mut slice);
    println!("{:?}", slice);
}

playground

This program outputs:

[1, 2, 3]
like image 23
Francis Gagné Avatar answered Oct 20 '22 01:10

Francis Gagné