Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
909 views
in Technique[技术] by (71.8m points)

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

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".

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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:


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...