【问题标题】:How to implement multiple mutable borrows of a vector in Rust? [duplicate]如何在 Rust 中实现向量的多个可变借用? [复制]
【发布时间】:2018-01-24 10:29:59
【问题描述】:

我正在用 Rust 实现矩阵。该代码已针对示例进行了修改,但可能存在一些小错误:

#[derive(Debug, PartialEq)]
pub struct Matrix<T> {
    inner: Vec<Vec<T>>,
}

impl<T> Matrix<T> {
    pub fn dim(&self) -> (usize, usize) {
        if self.inner.len() == 0 {
            (0, 0)
        } else {
            (self.inner.len(), self.inner[0].len())
        }
    }
}

我希望有能力得到矩阵的象限:

+----+----+
| Q1 | Q2 |
+----+----+
| Q3 | Q4 |
+----+----+

我引入了SliceSliceMut 结构来借用矩阵的一部分:

pub struct Slice<'a, T: 'a> {
    matrix: &'a Matrix<T>,
    start: (usize, usize),
    end: (usize, usize),
}

pub struct SliceMut<'a, T: 'a> {
    matrix: &'a mut Matrix<T>,
    start: (usize, usize),
    end: (usize, usize),
}

现在我要实现两个功能:

  • quadrants - 得到四个切片的元组
  • quadrants_mut - 获取四个可变切片的元组

我不能在quadrants_mut 中多次可变地借用一个矩阵:

fn quadrants_mut<'a, T>(matrix: &'a mut Matrix<T>) -> (SliceMut<'a, T>, SliceMut<'a, T>, SliceMut<'a, T>, SliceMut<'a, T>) {
    let (rows, cols) = matrix.dim();

    let mid_rows = rows / 2;
    let mid_cols = cols / 2;

    let a = SliceMut { matrix: matrix, start: (0, 0), end: (mid_rows, mid_cols) };
    let b = SliceMut { matrix: matrix, start: (0, mid_rows), end: (mid_cols, cols) };
    let c = SliceMut { matrix: matrix, start: (mid_rows, rows), end: (0, mid_cols) };
    let d = SliceMut { matrix: matrix, start: (mid_rows, rows), end: (mid_cols, cols) };

    (a, b, c, d)
}

当我尝试编译它时,我有一个错误:

error[E0499]: cannot borrow `*matrix` as mutable more than once at a time
  --> src/matrix/slice.rs:62:13
   |
59 |     let a = SliceMut { matrix: matrix, start: (0, 0), end: (mid_rows, mid_cols) };
   |                        ------ first mutable borrow occurs here
...
60 |     let b = SliceMut { matrix: matrix, start: (0, mid_rows), end: (mid_cols, cols) };
   |                        ^^^^^^ second mutable borrow occurs here
...
66 | }

我试图可变地借用一个矩阵四次。我应该如何更改代码以使其编译?

【问题讨论】:

  • 也许不是返回一个元组,而是返回一个结构,其中包含abcd,然后让该结构保存引用?不过,很可能有一种更惯用的方式来实现这一点。
  • Vec::split_at_mut 将是第一步。
  • 性能良好的矩阵不使用Vec&lt;Vec&lt;T&gt;&gt;,而是使用Vec&lt;T&gt; 来避免额外的间接性。
  • 矩阵实现可以用一个单个向量来实现,并且通过根据所需的列和行计算右索引来访问象限。

标签: rust


【解决方案1】:

Safe Rust 不允许同时拥有多个可变绑定。这是通过检查绑定类型(是否可变)和计数来实现的。编译器不太聪明,无法完全理解您的意图,因此可以告诉您使用的切片永远不会相交。通过在代码中包含多个可变引用,即使是对数据的不同部分,您仍然违反了规则。

作为一种解决方案,可以使用一个引用和索引来为您提供象限数据:它的beginend 索引,或者只是begincount

playground

pub struct SliceMut<'a, T: 'a> {
    matrix: &'a mut Matrix<T>,
    quadrants: Vec<(Range<usize>, Range<usize>)>,
}

fn quadrants_mut<'a, T>(matrix: &'a mut Matrix<T>) -> SliceMut<'a, T> {
    let (rows, cols) = matrix.dim();

    let mid_rows = rows / 2;
    let mid_cols = cols / 2;

    SliceMut {
        matrix: matrix,
        quadrants: vec![
            (0..0, mid_rows..mid_cols),
            (0..mid_rows, mid_cols..cols),
            (mid_rows..rows, 0..mid_cols),
            (mid_rows..rows, mid_cols..cols),
        ],
    }
}

至于split_at_mut,它使用了不安全的Rust并实现了as the following

#[inline]
fn split_at_mut(&mut self, mid: usize) -> (&mut [T], &mut [T]) {
    let len = self.len();
    let ptr = self.as_mut_ptr();

    unsafe {
        assert!(mid <= len);

        (from_raw_parts_mut(ptr, mid),
         from_raw_parts_mut(ptr.offset(mid as isize), len - mid))
    }
}

【讨论】:

    【解决方案2】:

    你想做的,绝对有可能。然而这很难。 真的很难。

    幸运的是,我只会展示如何做简单的部分。


    如果您查看split_at_mut 是如何实现的,您会注意到它需要您创建一个模拟quadrants_mut(即SliceMut)返回值的并行结构:

    pub struct SliceMut<'a, T: 'a> {
        matrix: &'a mut Matrix<T>,
        start: (usize, usize),
        end: (usize, usize),
    }
    
    #[repr(C)]
    struct MatrixRaw<T> {
        data: *const Matrix<T>,
        start: (usize, usize),
        end: (usize, usize),
    }
    

    注意这两种结构的相似之处。如果它们在任何时候出现分歧,您的 mem::transmute 将停止工作,或者您的安全代码将遇到段错误。

    然后我们创建一个将MatrixRaw 转换为SliceMut 的方法。

    #[inline]
    pub unsafe fn from_raw_mat_mut<'a, T>(
        p: *mut Matrix<T>,
        start: (usize, usize),
        end: (usize, usize),
    ) -> SliceMut<'a, T> {
        mem::transmute(MatrixRaw {
            data: p,
            start: start,
            end: end,
        })
    }
    

    作为最后一步,我们将unsafe 块添加到quadrant_mut

    unsafe {
        let a = from_raw_mat_mut(matrix, (0, 0), (mid_rows, mid_cols));
        let b = from_raw_mat_mut(matrix, (0, mid_rows), (mid_cols, cols));
        let c = from_raw_mat_mut(matrix, (mid_rows, rows), (0, mid_cols));
        let d = from_raw_mat_mut(matrix, (mid_rows, rows), (mid_cols, cols));
    
        (a, b, c, d)
    }
    

    Link to playground


    困难的部分:困难的部分来了 - 确保您的方法和迭代器不会意外地使您的数据和不变量无效。这在Matrix 案例中是非常难以实现的。

    为什么?好吧,因为没有一种很好的方式来对您的数据说“不要触摸这些部分”,就像使用数组一样。使用数组,您只需抵消您的数据,就可以了。但是Matrix?这并非不可能,但我怀疑我不知道一种不会引入性能损失的方法。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-08-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多