我有一个简单的结构,我想为其实现
Index
,但作为 Rust 的新手,我在借用检查器方面遇到了很多麻烦。 我的结构非常简单,我想让它存储一个起始值和步长值,然后当由 usize
索引时,它应该返回 start + idx * step
:
pub struct MyStruct {
pub start: f64,
pub step: f64,
}
我的直觉是,我只需获取
Index
的签名并插入我的类型即可:
impl Index<usize> for MyStruct {
type Output = f64;
fn index(&self, idx: usize) -> &f64 {
self.start + (idx as f64) * self.step
}
}
这会产生错误
mismatched types
,表示 expected type &f64, found type f64
。 作为一个尚未完全理解 Rust 类型系统如何工作的人,我尝试简单地在表达式上打 &
:
fn index(&self, idx: usize) -> &f64 {
&(self.start + (idx as f64) * self.step)
}
这现在告诉我,
borrowed value does not live long enough
,所以也许它需要一个生命周期变量?
fn index<'a>(&self, idx: usize) -> &'a f64 {
&(self.start + (idx as f64) * self.step)
}
错误是相同的,但注释现在给出了
lifetime 'a
而不是 lifetime #1
,所以我想这是没有必要的,但此时我感觉我被卡住了。 我很困惑,对于大多数语言来说,这样一个简单的练习在 Rust 中实现起来如此困难,因为我想做的就是从恰好位于引用后面的函数返回计算结果。 对于按需计算值的简单结构,我应该如何实施 Index
?
Index
特征旨在将借用的指针返回到 self
的成员(例如 Vec
中的项目)。 index
特征中的 Index
方法的签名使得实现它以具有您所描述的行为是不切实际的,因为您必须将 index
返回的每个值存储在 self
中,并确保指针保持有效,直到 MyStruct
被删除。
我正在努力解决同样的问题,并设法让它与下面的代码一起工作。
接受的答案有一个有效的观点,即这对于 Rust 开发人员来说可能不直观,因此使用后果自负。
use std::ops::Index;
/// Represent a single range of integer values
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub struct IntRange {
pub start: u32,
pub end: u32,
}
use std::cell::UnsafeCell;
/// Represent a list of ranges of integer values
///
/// This struct will allow us to store a list of ranges of integer values.
pub struct IntRangeList {
/// A range of ranges
pub ranges: Vec<IntRange>,
/// Field to cache our last computed index
last_retrieved_index: UnsafeCell<Option<u32>>,
}
impl IntRangeList {
pub fn new(ranges: Vec<IntRange>) -> IntRangeList {
IntRangeList {
ranges,
last_retrieved_index: UnsafeCell::new(None),
}
}
pub fn get_number_at_index(&self, index: u32) -> Option<u32> {
let mut mutable_index = index.clone();
for &range in &self.ranges {
println!("Finding index {}, Mutable index: {}", index, mutable_index);
let range_length = range.end - range.start + 1;
if mutable_index < range_length {
return Some(range.start + mutable_index);
}
mutable_index -= range_length;
}
None
}
}
/// Implement get index trait on IntRangeList.
///
/// This will allow us to use the index operator on IntRangeList.
/// An index will be used to get a single number from the list of IntRanges stored
/// in the IntRangeList.
impl Index<u32> for IntRangeList {
type Output = u32;
fn index(&self, index: u32) -> &Self::Output {
// Compute the number at the given index and store it in the last_retrieved_index
let number = self
.get_number_at_index(index)
.expect("Index out of bounds");
// SAFETY: We are storing the computed value in the last_retrieved_index and returning a reference to it.
// This is safe because the last_retrieved_index is part of the struct and will live as long as the struct.
unsafe {
let last_retrieved_index: &mut Option<u32> = &mut *self.last_retrieved_index.get();
*last_retrieved_index = Some(number);
last_retrieved_index.as_ref().unwrap()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_index_within_single_range() {
let range_list = IntRangeList::new(vec![IntRange { start: 10, end: 20 }]);
assert_eq!(range_list[0], 10);
assert_eq!(range_list[5], 15);
assert_eq!(range_list[10], 20);
}
#[test]
fn test_index_across_multiple_ranges() {
let range_list = IntRangeList::new(vec![
IntRange { start: 10, end: 20 },
IntRange { start: 30, end: 40 },
]);
assert_eq!(range_list[0], 10);
assert_eq!(range_list[10], 20);
assert_eq!(range_list[11], 30);
assert_eq!(range_list[20], 39);
assert_eq!(range_list[21], 40);
}
#[test]
#[should_panic(expected = "Index out of bounds")]
fn test_index_out_of_bounds() {
let range_list = IntRangeList::new(vec![IntRange { start: 10, end: 20 }]);
let _ = range_list[11]; // This should panic
}
}
此用例与
Index
的直觉不符。当我看到 myStruct[3]
时,我的直觉是,就像数组一样,我得到了一个指向一些已经初始化的数据的指针。 Index
的界面证实了这种直觉。
我可以看到您可能想要实现的两件事:
在这种情况下,我建议不要在实现
Index
的前提下,只提供一个返回 f64
而不是像这样的 &f64
的方法。
impl MyStruct {
pub fn index(&self, idx: usize) -> f64 {
self.start + (idx as f64) * self.step
}
}
你没有得到运算符,这很好,因为阅读
[]
的人会被误导,认为他们得到了一个指针。但您确实获得了您想要的功能。根据您的用例,您可能需要重命名此方法。
MyStruct
传递给具有 Index
边界的参数。这是有充分理由的棘手的。
Index
希望数据在它请求之前就已经存在。您无法生成并返回它,因为 index
返回 f64
,并且您无法在数据结构中生成它并返回指针,因为它不接受 &mut self
。您必须在调用 index
之前填充这些值。一些重新设计是有序的,重新设计的方向将取决于问题的更大背景。