【问题标题】:How to write a trait bound for adding two references of a generic type?如何编写用于添加两个泛型类型引用的特征绑定?
【发布时间】:2016-04-10 09:35:52
【问题描述】:

我有一个Fibonacci 结构,它可以用作任何实现OneZeroAddClone 的迭代器。这适用于所有整数类型。

我想将此结构用于BigInteger 类型,这些类型使用Vec 实现并且调用clone() 的成本很高。我想在对T 的两个引用上使用Add,然后返回一个新的T(然后不克隆)。

对于我的一生,我无法制作一个可以编译的...

工作:

extern crate num;

use std::ops::Add;
use std::mem;
use num::traits::{One, Zero};

pub struct Fibonacci<T> {
    curr: T,
    next: T,
}

pub fn new<T: One + Zero>() -> Fibonacci<T> {
    Fibonacci {
        curr: T::zero(),
        next: T::one(),
    }
}

impl<'a, T: Clone + Add<T, Output = T>> Iterator for Fibonacci<T> {
    type Item = T;

    fn next(&mut self) -> Option<T> {
        mem::swap(&mut self.next, &mut self.curr);
        self.next = self.next.clone() + self.curr.clone();
        Some(self.curr.clone())
    }
}

#[test]
fn test_fibonacci() {
    let first_12 = new::<i64>().take(12).collect::<Vec<_>>();
    assert_eq!(vec![1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144], first_12);
}

期望:

extern crate num;

use std::ops::Add;
use std::mem;
use num::traits::{One, Zero};

pub struct Fibonacci<T> {
    curr: T,
    next: T,
}

pub fn new<T: One + Zero>() -> Fibonacci<T> {
    Fibonacci {
        curr: T::zero(),
        next: T::one(),
    }
}

impl<'a, T: Clone + 'a> Iterator for Fibonacci<T>
where
    &'a T: Add<&'a T, Output = T>,
{
    type Item = T;

    fn next(&mut self) -> Option<T> {
        mem::swap(&mut self.next, &mut self.curr);
        self.next = &self.next + &self.curr;
        Some(self.curr.clone())
    }
}

#[test]
fn test_fibonacci() {
    let first_12 = new::<i64>().take(12).collect::<Vec<_>>();
    assert_eq!(vec![1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144], first_12);
}

这给出了错误

error[E0495]: cannot infer an appropriate lifetime for borrow expression due to conflicting requirements
  --> src/main.rs:27:21
   |
27 |         self.next = &self.next + &self.curr;
   |                     ^^^^^^^^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 25:5...
  --> src/main.rs:25:5
   |
25 | /     fn next(&mut self) -> Option<T> {
26 | |         mem::swap(&mut self.next, &mut self.curr);
27 | |         self.next = &self.next + &self.curr;
28 | |         Some(self.curr.clone())
29 | |     }
   | |_____^
note: ...so that reference does not outlive borrowed content
  --> src/main.rs:27:21
   |
27 |         self.next = &self.next + &self.curr;
   |                     ^^^^^^^^^^
note: but, the lifetime must be valid for the lifetime 'a as defined on the impl at 19:1...
  --> src/main.rs:19:1
   |
19 | / impl<'a, T: Clone + 'a> Iterator for Fibonacci<T>
20 | | where
21 | |     &'a T: Add<&'a T, Output = T>,
22 | | {
...  |
29 | |     }
30 | | }
   | |_^
note: ...so that types are compatible (expected std::ops::Add, found std::ops::Add<&'a T>)
  --> src/main.rs:27:32
   |
27 |         self.next = &self.next + &self.curr;
   |                                ^

error[E0495]: cannot infer an appropriate lifetime for borrow expression due to conflicting requirements
  --> src/main.rs:27:34
   |
27 |         self.next = &self.next + &self.curr;
   |                                  ^^^^^^^^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 25:5...
  --> src/main.rs:25:5
   |
25 | /     fn next(&mut self) -> Option<T> {
26 | |         mem::swap(&mut self.next, &mut self.curr);
27 | |         self.next = &self.next + &self.curr;
28 | |         Some(self.curr.clone())
29 | |     }
   | |_____^
note: ...so that reference does not outlive borrowed content
  --> src/main.rs:27:34
   |
27 |         self.next = &self.next + &self.curr;
   |                                  ^^^^^^^^^^
note: but, the lifetime must be valid for the lifetime 'a as defined on the impl at 19:1...
  --> src/main.rs:19:1
   |
19 | / impl<'a, T: Clone + 'a> Iterator for Fibonacci<T>
20 | | where
21 | |     &'a T: Add<&'a T, Output = T>,
22 | | {
...  |
29 | |     }
30 | | }
   | |_^
note: ...so that reference does not outlive borrowed content
  --> src/main.rs:27:34
   |
27 |         self.next = &self.next + &self.curr;
   |                                  ^^^^^^^^^^

【问题讨论】:

  • 您可能对std::ops::AddAssign 感兴趣,其 RFC 处于最终评论期:它允许您重载“+=”运算符。这至少可以避免 .clone() 调用添加。
  • 那将是一个少克隆 :) 我不能同时摆脱两者。 12 周以上,虽然我猜..
  • 实际上少了两个克隆:self.next = self.next.clone() + self.curr.clone(); 将被 self.next += &amp;self.curr; 替换。
  • 奇怪,我认为“由于要求冲突而无法推断寿命”列出了那些相互冲突的要求。

标签: generics rust fibonacci


【解决方案1】:

如何编写一个 trait bound 来添加两个泛型类型的引用?

让我们从一个简化的例子开始:

fn add_things<T>(a: &T, b: &T) {
    a + b;
}

这里有错误

error[E0369]: binary operation `+` cannot be applied to type `&T`
 --> src/lib.rs:2:5
  |
2 |     a + b;
  |     ^^^^^
  |
  = note: an implementation of `std::ops::Add` might be missing for `&T`

正如编译器提示的那样,我们需要保证为&amp;T 实现了Add。我们可以通过向我们的类型添加显式生命周期并在我们的 trait bounds 中使用它来直接表达这一点:

use std::ops::Add;

fn add_things<'a, T>(a: &'a T, b: &'a T)
where
    &'a T: Add,
{
    a + b;
}

接下来,让我们尝试一种稍微不同的方法——我们将在函数内部创建一个引用,而不是传递一个引用:

fn add_things<T>(a: T, b: T) {
    let a_ref = &a;
    let b_ref = &b;

    a_ref + b_ref;
}

我们得到同样的错误:

error[E0369]: binary operation `+` cannot be applied to type `&T`
 --> src/lib.rs:5:5
  |
5 |     a_ref + b_ref;
  |     ^^^^^^^^^^^^^
  |
  = note: an implementation of `std::ops::Add` might be missing for `&T`

但是,尝试添加与以前相同的修复程序不起作用。这也有点尴尬,因为生命周期与传入的任何参数都没有关联:

use std::ops::Add;

fn add_things<'a, T: 'a>(a: T, b: T)
where
    &'a T: Add,
{
    let a_ref = &a;
    let b_ref = &b;

    a_ref + b_ref;
}
error[E0597]: `a` does not live long enough
  --> src/lib.rs:7:17
   |
3  | fn add_things<'a, T: 'a>(a: T, b: T)
   |               -- lifetime `'a` defined here
...
7  |     let a_ref = &a;
   |                 ^^
   |                 |
   |                 borrowed value does not live long enough
   |                 assignment requires that `a` is borrowed for `'a`
...
11 | }
   | - `a` dropped here while still borrowed

'a 生命周期放在impl 上意味着该方法的调用者 可以确定生命周期应该是什么。由于引用是在方法内部进行的,调用者甚至无法看到生命周期是什么。

相反,您想设置一个限制,即任意生命周期的引用实现一个特征。这称为Higher Ranked Trait Bound (HRTB):

use std::ops::Add;

fn add_things<T>(a: T, b: T)
where
    for<'a> &'a T: Add,
{
    let a_ref = &a;
    let b_ref = &b;

    a_ref + b_ref;
}

应用回您的原始代码,您非常接近:

impl<T> Iterator for Fibonacci<T>
where
    T: Clone,
    for<'a> &'a T: Add<Output = T>,
{
    type Item = T;

    fn next(&mut self) -> Option<T> {
        mem::swap(&mut self.next, &mut self.curr);
        self.next = &self.next + &self.curr;
        Some(self.curr.clone())
    }
}

另见:

【讨论】:

  • 你知道如何消除最后一个克隆吗?直觉上,我认为实际数字必须在迭代器之外 ,但我无法使其工作。
  • @MatthieuM。我看不出如何消除该克隆,因为 current 值由结构保存,但我们也想从迭代器中返回它。我的下一个想法是返回对current 的引用,但这需要在self 上投入一生,这是不行的。你能扩展你的“迭代器之外”的想法吗?
  • 我的想法是创建一个状态 struct 来保存状态,然后创建另一个迭代器 struct 引用第一个 (&amp;mut) 并在它“前进”时对其进行变异,以便迭代器可以将参考资料返回国家;但我无法调整我的借款。
  • @MatthieuM。有趣的。像this 这样的东西?它似乎与将引用返回到迭代器本身具有相同的问题。看起来这两个实现必须是同构的,但我不能完全确定原因。
  • @MatthieuM。它必须归结为混叠。如果我们返回一个引用,则该引用必须在随后调用next 之前终止,否则就会出现别名。尤其是存在可变引用和不可变引用的糟糕类型。
猜你喜欢
  • 1970-01-01
  • 2021-10-09
  • 2021-07-02
  • 1970-01-01
  • 1970-01-01
  • 2022-12-15
  • 1970-01-01
  • 2020-12-20
  • 2023-01-12
相关资源
最近更新 更多