如何在Rust中实现向量的多个可变借位?

时间:2018-01-24 10:29:59

标签: rust

我正在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 | }

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

2 个答案:

答案 0 :(得分:0)

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

答案 1 :(得分:0)

你想做什么,绝对是可能的。但是很难。 真的 很难。

幸运的是,我只会展示如何做好事。

如果您查看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

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

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