gpt4 book ai didi

rust - Rust数据结构

转载 作者:行者123 更新时间:2023-12-03 11:43:53 26 4
gpt4 key购买 nike

我目前正在学习Rust,这很有趣。我在C/C++方面有一些经验,并且在其他使用更通用的范式(如泛型)的编程语言中也有其他经验。
背景
对于我的第一个项目(在本教程之后),我想创建一个N维数组(或Matrix)数据结构来练习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[&[2, 3]] = 23。由于逻辑彼此相似,因此这里是 IndexMutMatrix impl。
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通过从 &mut x返回 Some(x)来实现)。但我也希望 None设置值,即
let mut mat = Matrix::new(&[4, 4])
mat[&[2, 3]] = 23
问题
最后,是否有一种方法可以使 m[&[2,3]] = 23符合Vec结构分配内存的要求?如果不是的话,我应该改变什么,以及如何仍然有一个带有“空”点的阵列。在我尝试学习时,请接受任何建议。 :)
最终想法
通过我的研究,Vec结构暗示我已经键入了 T类型,并且必须对其进行大小调整。这对于通过 vec![pointer of T that is null but of size of T; total]分配适当大小的Vec可能很有用。但是我不确定该怎么做。

最佳答案

因此,有几种方法可以使它更类似于惯用的 rust ,但首先让我们看看为什么none分支没有意义。
因此,我将假定的OutputIndexMut类型为&mut T,因为您没有显示索引定义,但在这种假设下我觉得很安全。类型&mut T意味着对初始化的T的可变引用,这与C/C++中的指针可以指向已初始化或未初始化的内存不同。这意味着您必须返回一个初始化的T,由于没有初始化值,none分支无法执行此操作。这导致了第一种更惯用的方式。
返回一个Option<T>最简单的方法是将Index::Output更改为Option<T>。这样会更好,因为如果用户之前没有任何值(value)并且与您实际存储的内容接近,则用户可以决定该做什么。然后,您还可以在索引方法中消除 panic ,并允许调用方选择在没有值的情况下该怎么做。在这一点上,我认为您可以在下一个选项中对结构进行高级化处理。
直接存储T此方法允许调用者直接更改存储的类型,而不是将其包装在选项中。这样就可以很好地清理您的大多数索引代码,因为您只需要访问已存储的内容即可。现在的主要问题是初始化,您如何表示未初始化的值?您是正确的,该选项是执行此操作的最佳方法1,但是现在调用者可以通过自己存储一个Option来决定具有此可选的初始化功能。因此,这意味着我们始终可以存储已初始化的T,而不会失去功能。这只会真正更改您的新函数,而不会填充None值。我的建议是为新功能2绑定(bind)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<T>还为所有 T实现默认值并返回 None,因此该功能与您当前拥有的功能非常相似。
附加信息
当您刚接触在rust中,我可以对我以前陷入的陷阱发表一些评论。首先,您的结构包含对具有生命周期的尺寸的引用。这意味着您的结构不能比创建它们的维度对象存在更长的时间。到目前为止,传递的只是静态创建的尺寸,即键入代码中并存储在静态内存中的尺寸,这并没有给您带来任何问题。这使对象的生命周期为 'static,但是如果您使用动态尺寸,则不会发生这种情况。
您还可以如何存储这些尺寸,以便您的对象始终具有 'static生存期(与无生存期相同)?因为您要一个N维数组,所以堆栈分配是不可能的,因为堆栈数组在编译时必须是确定性的(在rust中也称为 const)。这意味着您必须使用堆。剩下两个实选项 Box<[usize]>Vec<usize>Box只是表示这是在堆上的另一种方式,并将 Sized添加到 ?Sized的值中。 Vec有点不言自明,并增加了调整大小的能力,但付出了一些额外的开销。要么允许您的矩阵对象始终具有 'static生存期。
  • 1.不区分Option<T>的另一种表示方法是MaybeUninit<T>,它是不安全的区域。这使您可以拥有一块足够大的初始化内存来容纳T,然后假定它是不安全地初始化的。这可能会导致很多问题,通常不值得这样做,因为Option已经进行了优化,如果它使用指针存储类型,则使用编译器魔术来存储该值是否为空指针的区别。
  • 2.本节不仅仅使用vec![Default::default(); total]的原因是,这需要T: Clone作为该宏的工作方式,因此第一部分被调用一次并被克隆,直到有足够的值为止。这是我们不需要的一个额外要求,因此没有它,界面将更加平滑。
  • 关于rust - Rust数据结构,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66235902/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com