我想写一个泛型函数,它接受任何不可变的可移植容器,比如数组、VecBTreeSet等等.因为这个函数是我正在实现的特性的一部分,所以我无法更改它的签名,所以不可能直接将迭代器作为参数,我也不能在函数签名中引入任何生命周期参数.

上下文

我试图在Rust中实现观察者模式.可观察者和观察者如下所示:

struct Observable<T> {
    value: T,
}

impl<T> Observable<T> {
    pub fn get(&self) -> &T {
        &self.value
    }
}

trait Observer<T> {
    fn update(&self, &Observable<T>);
}

(省略了一些与我的问题无关的函数)

现在,我的目标是编写一个观察器,它可以与任意iterable容器一起使用,这些容器包含可以赋值的项.它应该跟踪容器中项目值的总和,因此保存当前总和和计算任何项目值的函数.它应该实现Observer特征,这样每次容器更改时都可以更新总和.

use std::cell::RefCell;

struct SumObserver<T> {
    current_sum: RefCell<i64>,
    get_value: Fn(&T) -> i64,
}

到目前为止

很长一段时间以来,我一直试图编译update函数,但没有成功.以下是我try 过的函数的一个版本:

impl<'a, T, L> Observer<L> for SumObserver<T>
where
    &'a L: IntoIterator<Item = &'a T>,
{
    fn update(&self, observable: &Observable<L>) {
        let mut sum: i64 = 0;
        for item in observable.get() {
            sum += (self.get_value)(item);
        }
        *self.current_sum.borrow_mut() = sum;
    }
}

然而,编译器抱怨参数类型TL的生命周期 可能不够长:

error[E0309]: the parameter type `T` may not live long enough
  --> src/lib.rs:22:1
   |
22 |   impl<'a, T, L> Observer<L> for SumObserver<T>
   |   ^        - help: consider adding an explicit lifetime bound `T: 'a`...
   |  _|
   | |
23 | | where
24 | |     &'a L: IntoIterator<Item = &'a T>,
25 | | {
...  |
32 | |     }
33 | | }
   | |_^
   |
note: ...so that the reference type `&'a T` does not outlive the data it points at
  --> src/lib.rs:22:1
   |
22 | / impl<'a, T, L> Observer<L> for SumObserver<T>
23 | | where
24 | |     &'a L: IntoIterator<Item = &'a T>,
25 | | {
...  |
32 | |     }
33 | | }
   | |_^

如果整个函数体被注释掉,错误消息甚至保持不变.如果我也删除where条款,编译就可以了.

如果我按照编译器的建议为参数类型添加显式的生存期界限:

impl<'a, T: 'a, L: 'a> Observer<L> for SumObserver<T>

编译器给出以下错误:

error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
  --> src/lib.rs:28:32
   |
28 |         for item in observable.get() {
   |                                ^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #2 defined on the method body at 26:5...
  --> src/lib.rs:26:5
   |
26 | /     fn update(&self, observable: &Observable<L>) {
27 | |         let mut sum: i64 = 0;
28 | |         for item in observable.get() {
29 | |             sum += (self.get_value)(item);
30 | |         }
31 | |         *self.current_sum.borrow_mut() = sum;
32 | |     }
   | |_____^
note: ...so that reference does not outlive borrowed content
  --> src/lib.rs:28:21
   |
28 |         for item in observable.get() {
   |                     ^^^^^^^^^^
note: but, the lifetime must be valid for the lifetime 'a as defined on the impl at 22:6...
  --> src/lib.rs:22:6
   |
22 | impl<'a, T: 'a, L: 'a> Observer<L> for SumObserver<T>
   |      ^^
   = note: ...so that the types are compatible:
           expected std::iter::IntoIterator
              found std::iter::IntoIterator

我不明白这个函数的生命周期 有什么问题.在调用此函数的任何时候,编译器都应该确保borrow observable至少持续到函数返回为止.当时,任何observable英镑的借款都超出了范围.

推荐答案

这是高等级trait 界限(HRTB)的一个例子.

关键是,您不希望&Lone个生命周期中实现IntoIterator<Item = &T>,而是在L可能碰巧拥有的all个潜在生命周期中实现IntoIterator<Item = &T>.

在这种情况下,您需要使用更高级别的Trait-Bound:for<'a>将负责引入生存期名称,同时向编译器发出信号,使用它的子句应该对'a的所有可能值都有效.

这意味着:

impl<T, L> Observer<L> for SumObserver<T>
where
    for<'a> &'a L: IntoIterator<Item = &'a T>,
{
    fn update(&self, observable: &Observable<L>) {
        let mut sum: i64 = 0;
        for item in observable.get() {
            sum += (self.get_value)(item);
        }
        *self.current_sum.borrow_mut() = sum;
    }
}

编译(至少是单独编译).

另见:

Rust相关问答推荐

为什么Tauri要修改被调用函数的参数名称?

是否可以为`T:Copy`执行`T. clone`的测试

如何使用盒装枚举进行模式匹配?

函数内模块的父作用域的访问类型

在铁 rust 中,如何一次只引用几件事中的一件?

如何迭代属性以判断相等性?

是否提供Bundle 在可执行文件中的warp中的静态文件?

为什么`tokio::main`可以直接使用而不需要任何导入?

信号量释放后 Rust 输出挂起线程

如何在 Rust 中编写一个通用方法,它可以接受任何可以转换为另一个值的值?

为什么切片时需要参考?

没有得到无法返回引用局部变量`queues`的值返回引用当前函数拥有的数据的值的重复逻辑

错误:将自定义 proc_macro 与用Rust 的宝贝编写的属性一起使用时,无法在此范围内找到属性

将原始可变指针传递给 C FFI 后出现意外值

从现有系列和 map 值创建新系列

在 Rust 中如何将值推送到枚举 struct 内的 vec?

我如何将特征作为 struct 的拥有字段?

如何断言代码不会在测试中编译?

基于名称是否存在的条件编译

返回 &str 但不是 String 时,borrow 时间比预期长