【问题标题】:Communicating Rust Lifetimes for Type Parameters为类型参数传达 Rust 生命周期
【发布时间】:2019-04-08 05:37:02
【问题描述】:

我正在研究一个简单的复数示例,并尝试如下实现 ref-value/value-ref 操作:

use std::ops::*;

#[derive(Clone, PartialEq)]
pub struct Complex<T: Sized + Clone> {
    pub re: T,
    pub im: T,
}

// Ref-Ref Multiplication
impl<'a, 'b, T: Sized + Clone> Mul<&'b Complex<T>> for &'a Complex<T>
where
    T: Add<T, Output = T>,
    T: Sub<T, Output = T>,
    &'a T: Add<&'b T, Output = T>,
    &'a T: Mul<&'b T, Output = T>,
    &'a T: Sub<&'b T, Output = T>,
{
    type Output = Complex<T>;
    fn mul(self, rhs: &'b Complex<T>) -> Complex<T> {
        panic!("// Details irrelevant")
    }
}

// Ref-Value Multiplication
impl<'a, 'b, T: Sized + Clone> Mul<Complex<T>> for &'a Complex<T>
where
    T: 'static,
    T: Add<T, Output = T>,
    T: Sub<T, Output = T>,
    &'a T: Add<&'b T, Output = T>,
    &'a T: Mul<&'b T, Output = T>,
    &'a T: Sub<&'b T, Output = T>,
{
    type Output = Complex<T>;
    fn mul(self, rhs: Complex<T>) -> Complex<T> {
        let t = &rhs;
        self.mul(t)
    }
}

ref-ref 实现有效,据我了解,它需要两个不同生命周期的引用,并返回一个复杂的值类型。 ref-value 部分是我遇到问题的地方;当我编译时,错误是rhs 的寿命不够长。我相信我知道为什么已经这样了,那就是当返回值时 T 可以持有对 rhs 的引用(直接或间接),因此rhs 超出范围,但T 可以持有对还是这样。

我的问题是如何传达T 不会以某种形式或形式对rhs 进行某些引用。

关于我迄今为止尝试过或看过的东西的一些注释:

  1. 更改了任一 Mul 实现的生命周期规范。
  2. 尝试了生命周期继承,但这指定了 T 持有的引用将至少与 T 一样长,所以我认为我需要更多的“最多”行。
  3. 查看了其他实现;要么不实现案例,要么只使用克隆绕过问题。

【问题讨论】:

  • 可能您应该为Complex 派生Clone,然后为Complex 而不是&amp;Complex 实现操作。这不仅绕过了问题,而且通常比像这样的小结构的引用更快。
  • 我什至会更进一步,为复数类型派生Copy,因此您甚至不必显式克隆。
  • @SvenMarnach 谢谢,这实际上是我打算写的! (Copy,不是Clone):)
  • 您对&amp;'a T 的限制是不必要的。最后,您要将T 从另一个Complex&lt;T&gt; 中复制出来,并将它们移动到一个新的Complex&lt;T&gt; 中。这里没有办法引用原始的T,所以考虑对&amp;T的操作是没有意义的。
  • 你看起来像你想要的 impl AddAssign.

标签: rust lifetime type-parameter


【解决方案1】:

正如 Peter Hall 在 cmets 中所建议的,最简单的解决方案是为您的复杂类型派生 Copy,并实现值的操作。对于 ref-ref 实现和 ref-val 实现,您可以简单地取消引用引用并使用 val-val 实现。

如果你想让你开始的方法奏效,你需要更高等级的特征界限:

use std::ops::*;

#[derive(Clone, PartialEq)]
pub struct Complex<T: Clone> {
    pub re: T,
    pub im: T,
}

// Ref-Ref Multiplication
impl<'a, 'b, T: Clone> Mul<&'b Complex<T>> for &'a Complex<T>
where
    T: Add<T, Output = T>,
    T: Sub<T, Output = T>,
    &'a T: Add<&'b T, Output = T>,
    &'a T: Mul<&'b T, Output = T>,
    &'a T: Sub<&'b T, Output = T>,
{
    type Output = Complex<T>;
    fn mul(self, rhs: &'b Complex<T>) -> Complex<T> {
        Complex {
            re: &self.re * &rhs.re - &self.im * &rhs.im,
            im: &self.re * &rhs.im + &self.im * &rhs.re,
        }
    }
}

// Ref-Value Multiplication
impl<'a, T: Clone> Mul<Complex<T>> for &'a Complex<T>
where
    T: Add<T, Output = T>,
    T: Sub<T, Output = T>,
    &'a T: for<'b> Add<&'b T, Output = T>,
    &'a T: for<'b> Mul<&'b T, Output = T>,
    &'a T: for<'b> Sub<&'b T, Output = T>,
{
    type Output = Complex<T>;
    fn mul(self, rhs: Complex<T>) -> Complex<T> {
        let t = &rhs;
        self.mul(t)
    }
}

在您的版本中,引用值实现中的生命周期'b 由特征用户选择。由于用户可以使用 'b 的任何生命周期,rhs 需要静态生命周期才能使您的代码有效。相反,您想要的是 *'a T 满足给定生命周期 'bany 的给定特征界限,这正是 HRTB 的用途。

为第二个实现编写特征边界的另一种较少重复的方法是:

impl<'a, T: Clone> Mul<Complex<T>> for &'a Complex<T>
where
    Self: for<'b> Mul<&'b Complex<T>, Output = Complex<T>>,
{
    type Output = Complex<T>;
    fn mul(self, rhs: Complex<T>) -> Complex<T> {
        self.mul(&rhs)
    }
}

【讨论】:

  • 这里不需要 HRTB。在调用 mul 方法时,这里的所有生命周期都是已知的。
  • 是的,HRTB 正是我所需要的。太久了,我忘记了他们。谢谢!只是注意到我已经完成了价值价值的工作。只是没有显示,因为那是微不足道的,我有理由不这样做(在回答问题 cmets 时提到)。
  • @PeterHall 我不这么认为。那时局部变量t 的生命周期是未知的。
  • @Nim 我添加了一种更简洁的方式来编写特征边界。
【解决方案2】:

内置数字类型使用macro 实现这些排列。手动操作,我会从您将两个值相乘而不是任何引用的情况开始,并确保您的 Complex 结构是 Copy

impl<T: Copy> Mul<Complex<T>> for Complex<T>
where
    T: Add<T, Output = T>,
    T: Sub<T, Output = T>,
    T: Mul<T, Output = T>,
{
    type Output = Complex<T>;
    fn mul(self, rhs: Complex<T>) -> Complex<T> {
        unimplemented!()
    }
}

请注意,您不需要对&amp;T 进行任何限制——无论如何您都不能返回对T 的引用,因此您必须复制它们,这就是我指定T: Copy 的原因。

其余的实现现在很简单,可以委托给最简单的情况:

impl<'a, T: Copy> Mul<Complex<T>> for &'a Complex<T>
where
    T: Add<T, Output = T>,
    T: Sub<T, Output = T>,
    T: Mul<T, Output = T>,
{
    type Output = Complex<T>;
    fn mul(self, rhs: Complex<T>) -> Complex<T> {
        (*self).mul(rhs)
    }
}

impl<'a, T: Copy> Mul<&'a Complex<T>> for Complex<T>
where
    T: Add<T, Output = T>,
    T: Sub<T, Output = T>,
    T: Mul<T, Output = T>,
{
    type Output = Complex<T>;
    fn mul(self, rhs: &'a Complex<T>) -> Complex<T> {
        self.mul(*rhs)
    }
}

impl<'a, 'b, T: Copy> Mul<&'a Complex<T>> for &'b Complex<T>
where
    T: Add<T, Output = T>,
    T: Sub<T, Output = T>,
    T: Mul<T, Output = T>,
{
    type Output = Complex<T>;
    fn mul(self, rhs: &'a Complex<T>) -> Complex<T> {
        (*self).mul(*rhs)
    }
}

【讨论】:

  • 克隆/复制方法是我在其他实现中看到的,也是我知道的。我有几个原因要避免这种情况,主要是如果 T 是一个更复杂的结构(例如任意精度浮点数);如果 ref-ref 操作在这些上更快。还有一个简单的问题,正如你提到的,我不能返回对 T 的引用;你能详细说明一下吗? T不可能是可以包含T引用的结构吗?像struct A&lt;'a&gt; (Option&lt;&amp;'a A&gt;) 这样的东西我认为这就是为什么我的代码没有编译的原因,除非我弄错了。
  • @Nim 如果你想返回一个引用,它需要指向一个在mul()方法被调用之前就已经存在的值;您不能返回对您在方法内计算的新值的引用。
  • @SvenMarnach 这就是为什么我认为错误正在发生。我想要一些澄清,因为它的措辞一般来说是不能返回的,因此我的例子。只是想确保我没有错过一些微妙的东西,除了生命太短而无法接受。
猜你喜欢
  • 2022-08-22
  • 2013-07-03
  • 1970-01-01
  • 2019-12-27
  • 2021-05-24
  • 2017-05-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多