【问题标题】:Rust: how to use lifetimes when there are no references? [duplicate]Rust:没有引用时如何使用生命周期? [复制]
【发布时间】:2020-10-11 10:16:13
【问题描述】:

我有这个代码:

use std::hash::Hash;
use std::rc::Rc;

struct Counter<V> {
    value: V,
}

struct LFUCache<K, V> {
    values: Vec<(Rc<K>, Counter<V>)>,
}

impl<K: Hash + Eq, V> IntoIterator for LFUCache<K, V> {
    type Item = (Rc<K>, V);
    type IntoIter = Box<dyn Iterator<Item=(Rc<K>, V)>>;

    fn into_iter(self) -> Self::IntoIter {
        return Box::new(self
            .values
            .into_iter()
            .map(|(key, value_counter)| (key, value_counter.value)));
    }
}

我收到一个错误:

error[E0310]: the parameter type `K` may not live long enough
   --> src/lib.rs:167:16
    |
162 |   impl<K: Hash + Eq, V> IntoIterator for LFUCache<K, V> {
    |        -- help: consider adding an explicit lifetime bound `K: 'static`...
...
167 |           return Box::new(self
    |  ________________^
168 | |             .values
169 | |             .into_iter()
170 | |             .map(|(key, value_counter)| (key, value_counter.value)));
    | |____________________________________________________________________^
    |
note: ...so that the type `std::iter::Map<std::collections::hash_map::IntoIter<std::rc::Rc<K>, ValueCounter<V>>, [closure@src/lib.rs:170:18: 170:67]>` will meet its required lifetime bounds
   --> src/lib.rs:167:16
    |
167 |           return Box::new(self
    |  ________________^
168 | |             .values
169 | |             .into_iter()
170 | |             .map(|(key, value_counter)| (key, value_counter.value)));

我想表达盒装迭代器应该与 LFU 缓存一样长的意图。但是,由于没有引用,我无法获得任何生命周期。

我该如何解决这个问题?

【问题讨论】:

  • “由于没有引用,所以我无法获得任何生命周期” 不正确,您仍然可以引入新的生命周期参数并使用它们对其余部分应用生命周期约束的类型参数。它会像这样进行:impl&lt;'a, K: Hash + Eq + 'a, V: 'a&gt; IntoIterator for LFUCache&lt;K, V&gt;,而不会忘记关联类型的相同界限(type IntoIter = Box&lt;dyn Iterator&lt;Item=(Rc&lt;K + 'a&gt;, V)&gt; + 'a&gt; 等)
  • 这也有助于将来您在提问时创建一个正确的minimal reproducible example。这个缺少LFUCache的定义。
  • @E_net4likesdownvotes 感谢您的指点。 Rc&lt;K+'a&gt; 似乎无效,我收到一条错误消息,说它“不是特征”
  • 嗯,是的,应该只是Rc&lt;K&gt;。我的错。
  • @nz_21 我强烈建议您阅读Common Rust Lifetime Misconceptions,因为您的困惑源于一些常见的误解,这些误解很难在一个单一的答案中简明扼要地解决。

标签: rust iterator lifetime


【解决方案1】:

这并没有完全回答这个问题,但可能值得注意的是,使用盒装 trait 对象来保存返回的迭代器会导致生命周期的困难。如果直接返回迭代器,那就没问题了:

use std::hash::Hash;
use std::rc::Rc;
use std::{iter, vec};

struct Counter<V> {
    value: V,
}

struct LFUCache<K, V> {
    values: Vec<(Rc<K>, Counter<V>)>,
}

impl<K: Hash + Eq, V> IntoIterator for LFUCache<K, V> {
    type Item = (Rc<K>, V);
    type IntoIter =
        iter::Map<vec::IntoIter<(Rc<K>, Counter<V>)>, fn((Rc<K>, Counter<V>)) -> (Rc<K>, V)>;

    fn into_iter(self) -> Self::IntoIter {
        self.values
            .into_iter()
            .map(|(key, value_counter)| (key, value_counter.value))
    }
}

通过避免 Box 分配和 vtable 查找,这也可能具有更好的性能。这里的缺点是迭代器有一个相当复杂的类型,你必须编写它。最终,Rust 的“impl Trait”功能可能会被扩展以覆盖这种情况,这样您就不必显式地编写类型。事实上,使用 nightly Rust 你已经可以使用不稳定的特性来做到这一点:

#![feature(type_alias_impl_trait)]    

impl<K: Hash + Eq, V> IntoIterator for LFUCache<K, V> {
    type Item = (Rc<K>, V);
    type IntoIter = impl Iterator<Item = (Rc<K>, V)>;

    fn into_iter(self) -> Self::IntoIter {
        self.values
            .into_iter()
            .map(|(key, value_counter)| (key, value_counter.value))
    }
}

【讨论】:

    【解决方案2】:

    我想表达的意图是盒装迭代器应该与 LFU 缓存一样长。

    方法into_iter 消耗 self,所以这没有意义。只要调用into_iter(),LFU 缓存就会消失。

    如果你想保留它,那么你可以为&amp;LFUCache 实现IntoIterator。现在你有了一个引用——也就是一个生命周期——迭代器可以绑定到它。请注意,迭代将移动V 类型的值,因此您需要将其限制为Copy(或至少Clone),或者使迭代器项包含对V 的引用。这是使用引用的工作示例:

    impl<'a, K: Hash + Eq, V> IntoIterator for &'a LFUCache<K, V> {
        type Item = (Rc<K>, &'a V);
        type IntoIter = Box<dyn Iterator<Item=(Rc<K>, &'a V)> + 'a>;
    
        fn into_iter(self) -> Self::IntoIter {
            return Box::new(self
                .values
                .iter()
                .map(|(key, value_counter)| (key.clone(), &value_counter.value)));
        }
    }
    

    【讨论】:

    • > "方法 into_iter 消耗 self,所以这没有意义。" 那么它对向量如何工作?这样做是完全有效的 for value in v.into_iter() {} 在调用 into_iter() 时向量正在被消耗,但我仍然可以获得它的基础值
    • 这也会消耗矢量,@nz_21。
    • 我的最终目标是实现这一点:for (key, value) in lfu.into_iter() {} 消耗 lfu 并移出键和值。键是Rc&lt;K&gt; 类型,值是V
    • 确实如此。如果v 是一个向量,那么v.into_iter() 会消耗它,所以你以后不能使用它:playground example
    • 也许你需要重新阅读你的问题(包括标题和我引用的部分),如果它不能传达你真正想问的问题,可能会重新编写它。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-12-13
    • 1970-01-01
    • 2013-07-03
    • 2020-04-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多