【问题标题】:Rust Data StructureRust 数据结构
【发布时间】:2021-05-19 23:38:28
【问题描述】:

我目前学习 Rust 是为了好玩。我在 C/C++ 方面有一些经验,并且在使用泛型等更复杂范式的其他编程语言方面有其他经验。

背景

对于我的第一个项目(在教程之后),我想创建一个 N 维数组(或矩阵)数据结构来练习使用 Rust 进行开发。

这是迄今为止我的 Matrix 结构以及基本填充和新初始化的内容。

原谅缺少的边界检查和参数测试

pub struct Matrix<'a, T> {
    data: Vec<Option<T>>,
    dimensions: &'a [usize],
}

impl<'a, T: Clone> Matrix<'a, T> {
    pub fn fill(dimensions: &'a [usize], fill: T) -> Matrix<'a, T> {
        let mut total = if dimensions.len() > 0 { 1 } else { 0 };
        for dim in dimensions.iter() {
            total *= dim;
        }
        Matrix {
            data: vec![Some(fill); total],
            dimensions: dimensions,
        }
    }

    pub fn new(dimensions: &'a [usize]) -> Matrix<'a, T> {
        ...
        Matrix {
            data: vec![None; total],
            dimensions: dimensions,
        }
    }
}

我希望能够使用 New fn 创建一个“空”N 维数组。我认为使用 Option 枚举将是实现此目的的最佳方式,因为我可以用 None 填充 N 维,它会自动为这个 T 泛型分配空间。

那么归根结底就是能够为此设置条目。我发现IndexMutIndex 特征看起来我可以做类似m[&amp;[2, 3]] = 23 的事情。由于逻辑彼此相似,这里是IndexMut impl for Matrix

impl<'a, T> ops::IndexMut<&[usize]> for Matrix<'a, T> {
    fn index_mut(&mut self, indices: &[usize]) -> &mut Self::Output {
        match self.data[get_matrix_index(self.dimensions, indices)].as_mut() {
            Some(x) => x,
            None => {
                NOT SURE WHAT TO DO HERE.
            }
        }
    }
}

理想情况下会发生的情况是值(如果存在)将被更改,即

let mut mat = Matrix::fill(&[4, 4], 0)
mat[&[2, 3]] = 23

这会将值设置为 0 到 23(上面的 fn 通过从 Some(x) 返回 &amp;mut x 来实现)。但我也希望None 设置值,即

let mut mat = Matrix::new(&[4, 4])
mat[&[2, 3]] = 23

问题

最后,有没有办法让m[&amp;[2,3]] = 23 成为可能,因为 Vec 结构需要分配内存?如果不是我应该改变什么,我怎么还能有一个带有“空”点的数组。在我尝试学习时接受任何建议。 :)

最后的想法

通过我的研究,Vec struct impls 我看到类型 T 是类型化的并且必须调整大小。这对于通过vec![pointer of T that is null but of size of T; total] 分配具有适当大小的 Vec 可能很有用。但我不确定如何做到这一点。

【问题讨论】:

  • 您对Index 特征有什么看法?如果该索引处的值不存在,你会返回什么?
  • 我之所以问是因为可以根据需要实现IndexMut,但由于IndexMut 的输出类型必须匹配相应的Index 特征,因此它会对Index 实现产生令人不安的影响。
  • 对于Index 特征,如果Option&lt;T&gt; 枚举的值为None,我也会遇到同样的问题。我不知道该返回什么。目前我只是惊慌失措,但我不确定该怎么做这对 Rust 来说是惯用的,并且具有返回“空”指针或对用户有意义的东西的功能。
  • 理想情况下,这意味着在 println!("{}", m[&amp;[1, 1]]); 等情况下,m[&amp;[1, 1]] 是选项枚举 None 它会出错,因为它会对“null”值或类似的东西执行 to_string .但对于功能,Index 应该返回矩阵中该位置的值。

标签: rust


【解决方案1】:

所以有几种方法可以让这更类似于惯用的 rust,但首先,让我们看看为什么 none 分支没有意义。

所以我假设IndexMutOutput 类型是&amp;mut T,因为您没有显示索引定义,但我在这个假设中感到安全。 &amp;mut T 类型表示对 已初始化 T 的可变引用,这与 C/C++ 中的指针不同,它们可以指向已初始化或未初始化的内存。这意味着您必须返回一个初始化的T,因为没有初始化值,所以 none 分支不能。这导致了第一种更惯用的方式。

返回一个Option&lt;T&gt;

最简单的方法是将Index::Output 更改为Option&lt;T&gt;。这更好,因为用户可以决定如果之前没有价值并且接近您实际存储的内容时该怎么做。然后,您还可以消除 index 方法中的恐慌,并允许调用者选择在没有值时执行的操作。在这一点上,我认为您可以在下一个选项中进一步升级结构。

直接存储T

此方法允许调用者直接更改存储的类型,而不是将其包装在选项中。这很好地清理了您的大部分索引代码,因为您只需要访问已经存储的内容。现在的主要问题是初始化,如何表示未初始化的值?您是正确的,该选项是执行此操作的最佳方法1,但现在调用者可以通过自己存储Option 来决定具有此可选初始化功能。这意味着我们可以始终存储初始化的Ts 而不会丢失功能。这只会真正改变您的新功能,而不是填充 None 值。我的建议是为新函数2绑定T: Default

impl<'a, T: Default> Matrix<'a, T> {
  pub fn new(dimensions: &'a [usize]) -> Matrix<'a, T> {
    Matrix {
      data: (0..total).into_iter().map(|_|Default::default()).collect(),
      dimensions: dimensions,
    }
  }
}

此方法在 rust 世界中更为常见,它允许调用者选择是否允许未初始化的值。 Option&lt;T&gt; 还实现了所有 T 的默认值并返回 None 因此功能与您当前的功能非常相似。

附加信息

由于您是 rust 新手,因此我可以制作一些关于我之前掉入的陷阱的 cmets。开始你的结构包含对具有生命周期的维度的引用。这意味着您的结构不能存在的时间长于创建它们的维度对象。到目前为止,这并没有给您带来问题,因为您传递的只是静态创建的维度,这些维度是输入代码并存储在静态内存中的。这使您的对象的生命周期为'static,但如果您使用动态尺寸,则不会发生这种情况。

您还能如何存储这些维度,以便您的对象始终具有'static 生命周期(与没有生命周期相同)?由于您想要一个 N 维数组堆栈分配是不可能的,因为堆栈数组在编译时必须是确定性的(在 rust 中也称为 const)。这意味着您必须使用堆。这留下了两个实物期权Box&lt;[usize]&gt;Vec&lt;usize&gt;Box 只是另一种说法,这是在堆上,并将 Sized 添加到 ?Sized 的值。 Vec 更加不言自明,并增加了以少量开销为代价调整大小的能力。两者都可以让您的矩阵对象始终拥有'static 生命周期。

  • 1。在没有Option&lt;T&gt; 的歧视的情况下表示这一点的另一种方式是MaybeUninit&lt;T&gt;,这是不安全的领域。这允许你有一块足够大的初始化内存来保存T,然后假设它被不安全地初始化。这可能会导致很多问题并且通常不值得这样做,因为Option 已经进行了高度优化,因为如果它使用指针存储类型,它会使用编译器魔法来存储该值是否为空指针的判别。

  • 2。本节不只使用vec![Default::default(); total] 的原因是这需要T: Clone,因为该宏的工作方式第一部分被调用一次并被克隆,直到有足够的值。这是我们不需要的额外要求,因此没有它界面会更流畅。

【讨论】:

  • 感谢所有建议,是的,这是我掉入的陷阱。我喜欢Box&lt;[usize]&gt; 的想法,因为我不希望矩阵在初始化后能够调整大小(也许我会添加一个函数来克隆具有新大小的矩阵)。至于在矩阵中存储T,由于其他功能(例如打印矩阵的字符串),我在Option&lt;T&gt;Default::default() 之间纠结。默认情况下,这会导致填充矩阵 Matrix::fill([4,4], 0)Matrix::new([4,4]) 之间出现混淆,因为它们是相同的。所以倾向于Option&lt;T&gt;
猜你喜欢
  • 1970-01-01
  • 2020-09-15
  • 2022-01-15
  • 1970-01-01
  • 1970-01-01
  • 2016-07-02
  • 2019-06-06
  • 1970-01-01
  • 2022-12-03
相关资源
最近更新 更多