我如何摆脱这个 "argument requires that borrow lasts for `'1`" 错误?

How do I get rid of this "argument requires that borrow lasts for `'1`" error?

我正在编写我的第一个 Rust 程序,实现一个简单的 LruCache 并处理一些事件。

LruCache 在闭包中使用,以跟踪轮询后已处理的事件,如果已处理则跳过它们:

let mut evt_cache = LruSetCache::new(100_000);

// poll for events
loop {

    let unprocessed_events: Vec<Event> = unprocessed_events_serialized
        .into_iter()
        .filter_map(|evt| {
            let key = evt.as_bytes();

            if evt_cache.exists(&key) {
                None
            } else {
                evt_cache.put(key.clone());
                Some(Event::from_bytes(&evt.as_bytes()).unwrap())
            }
        })
        .collect();

    // ... process events

}

但是,编译器对我将 [u8] 键传递给缓存的方式不满意:

  --> src/main.rs:58:27
   |
51 |     let mut evt_cache = LruSetCache::new(100_000);
   |         ------------ lifetime `'1` appears in the type of `evt_cache`
...
58 |                 let key = evt.as_bytes();
   |                           ^^^^^^^^^^^^^ borrowed value does not live long enough
...
63 |                     evt_cache.put(key);
   |                     ----------------- argument requires that `evt` is borrowed for `'1`
...
66 |             })
   |             - `evt` dropped here while still borrowed

LruSetCache 可能是罪魁祸首:

use std::collections::{HashSet, VecDeque};
use std::hash::Hash;

/// A LruCache that stores values (instead of (key, values) pairs).
///
/// You can
/// - insert elements,
/// - check for existence of an item.
/// It automatically frees memory after max_capacity is reached.
///
/// Basically, an LRUCache Backed by an `HashSet` instead of an `HashMap` that saves (little) memory and (very few) cpu cycles.
pub struct LruSetCache<T> {
    /// Elements will start to be evicted from the cache when this number of elements is reached.
    ///
    /// Note: In theory, we could avoid having this field here and keep track of the capacity by
    /// initializing the underlying `items` with `HashSet::with_capacity()` and by querying
    /// `HashSet::capacity()` when needed. However, if this inner detail implementation of `HashSet`
    /// that grows the capacity and starts re-allocating when the element is close to being full
    /// (rather than being full), then the LruCache would break.
    capacity: usize,

    /// Tracks which elements were added first, because they'll need to be removed when the
    /// maximum capacity is reached.
    /// Keeps track of the current capacity (`items.len()`).
    items: VecDeque<T>,

    /// Allows fast item existence check (O(1) as opposed to O(n) with the above VecDeque).
    items_set: HashSet<T>,
}

impl<T> LruCache<T> {
    pub fn new(capacity: usize) -> LruCache<T> {
        LruCache {
            capacity,
            items: VecDeque::with_capacity(capacity),
            items_set: HashSet::with_capacity(capacity),
        }
    }

    /// Checks whether an item exists.
    pub fn exists(&self, item: &T) -> bool {
        self.items_set.get(item).is_some()
    }

    /// Inserts an item.
    pub fn put(&mut self, item: T) {
        if self.items.len() >= self.capacity {
            // evict item that was inserted least recently
            self.items.pop_back();
            self.items_set.remove(&item);
        }

        self.items.push_front(&item);
        self.items_set.insert(&item);
    }
}

到目前为止我的调试之旅

我通过指定 T 参数而不是 LruSetCache::set 中的 &T 将所有权传递给 LruCache::put 函数,但这似乎还不够Rust 让它在闭包结束后继续存在。据我了解,move 只会创建另一个指向原始值的指针,所以我明白为什么这还不够。

所以我在传入之前尝试了 .clone()ing 字符串。我知道错误是 [u8] 键在闭包结束时内存不足。但是,重新克隆并传递它并没有帮助。

我猜这是因为它是在堆栈中克隆的,而不是在可以逃避函数清理的堆中。

所以我已经尝试 Boxing 键,所以它会存在于堆中而不是堆栈中,并且在函数 returns 时不会被清除。但我得到的错误信息基本相同:

    let key = Box::new(evt.as_bytes());
53 |     let mut evt_cache = LruSetCache::new(100_000);
   |         ------------- lifetime `'1` appears in the type of `evt_cache`
...
60 |                 let key = Box::new(evt.as_bytes());
   |                                    ^^^^^^^^^^^^^^ borrowed value does not live long enough
...
65 |                     evt_cache.put(key);
   |                     ------------------ argument requires that `eat` is borrowed for `'1`
...
68 |             })
   |             - `evt` dropped here while still borrowed

Rust 出于某种原因希望 evt 比闭包更有效,但我只需要密钥即可。

这是怎么回事?

尝试用 .to_owned().to_vec() 替换 .clone()

let unprocessed_events: Vec<Event> = unprocessed_events_serialized
    .into_iter()
    .filter_map(|evt| {
        let key = evt.as_bytes().to_owned(); // add `.to_vec()` or `.to_owned()`

        if evt_cache.exists(&key) {
            None
        } else {
            evt_cache.put(key); // Can remove .clone()
            Some(Event::from_bytes(&evt.as_bytes()).unwrap())
        }
    })
    .collect();

这将克隆整个密钥,因此如果您的密钥非常大,可能不适合。

以上版本将始终 克隆整个密钥,即使它已经存在于evt_cache 中。如果 .exists() 函数在 Borrow<T> 上是通用的,而不是仅仅接受引用,则可以(可能)解除此限制。 (查看 HashSet::contains 是如何定义的 https://doc.rust-lang.org/std/collections/struct.HashSet.html#method.contains)。这样你只需要在 evt_cache.put.

上调用 .to_owned()

Shouldn't .clone() and .to_vec() do the same thing here?

克隆特性的定义方式使我们无法生成与传入的类型不同的类型。这会导致 &[T]&str、[= 等类型出现问题23=] 等,通过附加的 ToOwned 特性解决。

来自 https://doc.rust-lang.org/std/borrow/trait.ToOwned.html

A generalization of Clone to borrowed data.

Some types make it possible to go from borrowed to owned, usually by implementing the Clone trait. But Clone works only for going from &T to T. The ToOwned trait generalizes Clone to construct owned data from any borrow of a given type.

因此 &[u8].to_owned() 确实如您所愿,它只是调用 .to_vec()

https://doc.rust-lang.org/src/alloc/slice.rs.html#838-862