实现 Index 特征以返回非引用的值

问题描述 投票:0回答:3

我有一个简单的结构,我想为其实现

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

indexing rust
3个回答
4
投票

Index
特征旨在将借用的指针返回到
self
的成员(例如
Vec
中的项目)。
index
特征中的
Index
方法的签名使得实现它以具有您所描述的行为是不切实际的,因为您必须将
index
返回的每个值存储在
self
中,并确保指针保持有效,直到
MyStruct
被删除。


0
投票

我正在努力解决同样的问题,并设法让它与下面的代码一起工作。

接受的答案有一个有效的观点,即这对于 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
    }
}

-1
投票

此用例与

Index
的直觉不符。当我看到
myStruct[3]
时,我的直觉是,就像数组一样,我得到了一个指向一些已经初始化的数据的指针。
Index
的界面证实了这种直觉。

我可以看到您可能想要实现的两件事:

  1. 为您的数据结构获取良好的索引语法。

在这种情况下,我建议不要在实现

Index
的前提下,只提供一个返回
f64
而不是像这样的
&f64
的方法。

impl MyStruct {
    pub fn index(&self, idx: usize) -> f64 {
        self.start + (idx as f64) * self.step
    }
}

你没有得到运算符,这很好,因为阅读

[]
的人会被误导,认为他们得到了一个指针。但您确实获得了您想要的功能。根据您的用例,您可能需要重命名此方法。

  1. MyStruct
    传递给具有
    Index
    边界的参数。

这是有充分理由的棘手的。

Index
希望数据在它请求之前就已经存在。您无法生成并返回它,因为
index
返回
f64
,并且您无法在数据结构中生成它并返回指针,因为它不接受
&mut self
。您必须在调用
index
之前填充这些值。一些重新设计是有序的,重新设计的方向将取决于问题的更大背景。

最新问题
© www.soinside.com 2019 - 2024. All rights reserved.