如何修改作为函数参数的切片?

14

函数可以接受参数并进行修改:

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

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

输出

42

将代码更改为使用切片会出现大量错误:

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

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

输出

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

如果我们尝试使用一个可变的切片(这并不是我真正想要的;我不想修改切片内部的值,我只想修改切片本身以覆盖更小范围的元素),和一个可变参数,它对原始切片没有影响:

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);
}

输出

[0, 1, 2, 3]

有没有办法修改作为函数参数的切片?我不想修改切片中的元素;我只想修改切片本身的范围,使其成为一个更小的“子切片”。
3个回答

16
如其他人所说,这里的核心思想是获取一个&mut &... [T](其中...mut或为空),并读/写内部切片。其他答案证明了在安全代码中使用&mut &[T]是可能的,在不安全代码中使用&mut &mut [T]也是可能的,但它们没有解释为什么会有差异......而&mut &mut [T]也可以在安全代码中实现。
在显式生命周期术语中,嵌套引用类似于&'a mut &'b ... [T],其中'a'b是某些生命周期,并且目标是获取&'b ... [T],对其进行切片并将其写入&'a mut中。
对于 &'a mut &'b [T],这很容易实现: &[T] 是复制的,所以编写 *slice = &slice[1..] 将有效地将 &'b [T]&mut 中复制出来,然后稍后用较短的值覆盖现有值。复制意味着一个字面上得到了一个 &'b [T] 来操作,因此它与 &'a mut 之间没有直接联系,因此可以进行变异操作。这实际上类似于
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.)
对于 &'a mut &'b mut [T],情况会有些棘手:&mut [T] 无法复制:解引用不会复制,而是重新借用以给出一个 &'a mut [T],即切片具有与 外部&'a mut 相关联的生命周期,而不是内部的 &'b mut [T]。这意味着被切割的引用的生命周期比它试图覆盖的类型更短,因此将切片存储到该位置是无效的。换句话说:
fn pop_front<'a, 'b>(slice: &'a mut &'b mut [i32]) {
    let value: &'a mut [i32] = &mut **slice;
    *slice = &mut value[1..] // error
}

要安全地处理 &'a mut &'b mut [T],需要使用具有 'b 生命周期的引用来获取内部切片。这需要遵循“一个所有者”规则,不进行借用,并且针对此类所有权操作的最佳功能是 mem::replace。它允许我们通过将其与某个占位符交换来提取内部的 &'b mut [T],然后我们可以用短版本重写它。最好/唯一的占位符是空数组:编写 &mut [] 可以是任何类型 X 和任何生命周期 'c&'c mut [X],因为没有数据需要存储,所以不需要初始化,也不会出现无效数据。特别地,它可以是一个 &'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..]
}

由于&mut[T]实现了Default,我们也可以使用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..]
}

如上所述,我将事情变得比必要的更明确。

另请参见:


5
如果您需要修改不可变的切片,请参见Cornstalks的答案
在安全的Rust中,您无法修改可变切片。当您从可变切片中获取子切片时,实际上是从原始切片中借用。这意味着子切片不能超出原始切片的生命周期。
您需要的是类似于这样的东西:
fn pop_front(slice: &mut &mut [i32]) {
    *slice = &mut slice[1..];
}

但是子切片slice[1..]仅在函数结束之前有效,在此时,借用将结束并且原始切片(slice参数)将再次可用。
我们可以使用一些unsafe代码手动构建所需的切片:
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

这个程序输出:

[1, 2, 3]

4

使用Francis Gagné的答案中的部分(我没有考虑尝试&mut &),我能够在不使用unsafe代码的情况下让它正常工作:

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);
}

输出

[1, 2, 3]

这会改变一个不可变的切片,而不是一个可变的切片。从问题中并不清楚OP的需求是什么... - Francis Gagné
@FrancisGagné:这就是我在我的问题中想要询问的内容,但说实话,在我的真实代码中我使用了可变切片,所以你的答案对我非常有帮助。我在尝试合理地谈论可变不可变切片时遇到了困难,因为“可变不可变”听起来毫无意义(但可能是正确的术语)。 - Cornstalks
你的 pop_front 接受一个不可变 切片 的可变 引用。我的 pop_front 接受一个可变切片的可变引用。 - Francis Gagné
@FrancisGagné:那个措辞听起来好多了。感谢你的所有帮助!我希望我能给你更多的赞。 - Cornstalks

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接