"溢出评估需求"但这种递归根本不应该发生

Dan*_*iel 6 generics rust

这是一个冗长的例子,因为我无法进一步减少它.铁锈游乐场

use std::marker::PhantomData;
use std::ops::{Add, Sub, Mul, Div};

pub trait Scalar
    : Sized + Copy + Add<Self, Output = Self> + Sub<Self, Output = Self> + Mul<Self, Output = Self> + Div<Self, Output = Self>
    {}
impl Scalar for u32 {}

pub struct ScalarVal<T>(T) where T: Scalar;

pub trait Pixel: Sized {
    type ScalarType: Scalar;
}

#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Gray<BaseTypeP>
    where BaseTypeP: Scalar
{
    intensity: BaseTypeP,
}

impl<BaseTypeP> Pixel for Gray<BaseTypeP>
    where BaseTypeP: Scalar
{
    type ScalarType = BaseTypeP;
}

impl<BaseTypeP> Add<Gray<BaseTypeP>> for ScalarVal<BaseTypeP>
    where BaseTypeP: Scalar
{
    type Output = Gray<BaseTypeP>;
    fn add(self, rhs: Gray<BaseTypeP>) -> Gray<BaseTypeP> {
        unimplemented!()
    }
}

pub struct Image<PixelP>
    where PixelP: Pixel
{
    _marker: PhantomData<PixelP>,
}

impl<PixelP> Add<Image<PixelP>> for ScalarVal<<PixelP as Pixel>::ScalarType>
    where PixelP: Pixel,
          ScalarVal<<PixelP as Pixel>::ScalarType>: Add<PixelP, Output = PixelP>
{
    type Output = Image<PixelP>;
    fn add(self, rhs: Image<PixelP>) -> Image<PixelP> {
        unimplemented!()
    }
}

fn main() {
    let a = Gray::<u32> { intensity: 41 };
    let b = ScalarVal(1) + a;
}
Run Code Online (Sandbox Code Playgroud)

有人可以解释我为什么要进入overflow evaluating the requirement <_ as Pixel>::ScalarType该代码段吗?

我很困惑,因为:

  • 如果Add删除第44行中的实现,则代码编译正常.但是不应该使用该实现=> main()仅使用Gray而不是Image
  • 递归似乎是在嵌套Image<Image<...>>结构中,但这根本不应该发生?!
  • 如果第46行改为ScalarVal<<PixelP as Pixel>::ScalarType>: Add编译就好 - 但为什么呢?

一些背景

这是我想要构建的图像处理库的一部分.我们的想法是拥有可用于图像的不同像素格式(灰色,RGB,拜耳......).因此,你有Image<Gray>一个灰度图像.不同的Pixel实现可以实现不同的运算符,因此您可以gray_a = gray_b + gray_c使用它们进行计算(例如).也可以在这些实现中使用标量值来进行例如gray_a = gray_b + ScalarVal(42).因为我希望能够进行ScalarVal右手左手参数,所以需要有两个实现(impl Add<Gray<...>> for ScalarVal<...>impl Add<ScalarVal<...>> for Gray<...>).

好的,现在该Image类型应该实现所使用Pixel类型支持的所有运算符.如果有可能,gray_pixel + Scalar(42)也应该可以这样做gray_image + Scalar(42).

希望这种有道理.

Vee*_*rac 6

ScalarVal(1) + a基本上解析为<ScalarVal(1) as Add>.add(a),寻找Add实现ScalarVal.

无论出于何种原因,首先检查这个:

impl<PixelP> Add<Image<PixelP>> for ScalarVal<<PixelP as Pixel>::ScalarType>
    where PixelP: Pixel,
          ScalarVal<<PixelP as Pixel>::ScalarType>: Add<PixelP, Output = PixelP>
Run Code Online (Sandbox Code Playgroud)

PixelP此时未实例化,因此PixelP: Pixel无法检查.因此我们得到了

ScalarVal<<PixelP as Pixel>::ScalarType>: Add<PixelP, Output = PixelP>
Run Code Online (Sandbox Code Playgroud)

让我们简化一下.既然PixelP现在还不知道,<PixelP as Pixel>::ScalarType还不得而知.编译器已知的实际信息看起来更像

impl<T> Add<Image<T>> for ScalarVal<_>
    where ScalarVal<_>: Add<T, Output = T>
Run Code Online (Sandbox Code Playgroud)

所以ScalarVal<_>寻找一个Add<T, Output = T>.这意味着我们应该寻找合适的T.显然,这意味着在寻找ScalarValAdd impl秒.看着同一个,我们得到了

impl<T2> Add<Image<T2>> for ScalarVal<_>
    where ScalarVal<_>: Add<T2, Output = T2>
Run Code Online (Sandbox Code Playgroud)

这意味着如果这个匹配,T == Image<T2>.

显然,然后T2 == Image<T3>,T3 == Image<T4>等这导致溢出和一般的悲伤.Rust永远不会发现防伪,因此无法保证它会走错路.