【问题标题】:"unconstrained type parameter" for `impl From<Struct> for T` when `impl Struct` works fine当`impl Struct`工作正常时,`impl From<Struct> for T`的“无约束类型参数”
【发布时间】:2021-08-03 16:38:53
【问题描述】:

polars 库实现以下方法将其转换为数组:

impl DataFrame {
    pub fn to_ndarray<N>(&self) -> Result<Array2<N::Native>>
    where
        N: PolarsNumericType,
        N::Native: num::Zero + Copy,
    {}
}

我想通过使用这个函数来实现一个特征来使它更有用:

impl<N> From<DataFrame> for Array2<N::Native>
    where
        N: PolarsNumericType,
        N::Native: num::Zero + Copy
{}

这给了我:

129 | impl<N> From<DataFrame> for Array2<N::Native>
    |      ^ unconstrained type parameter

这是一个众所周知的问题,有上百万个问题。

有人能用英文解释一下为什么编译器会在这里混淆吗?

在我看来,一切都受到了限制:DataFrame 可以转换为任何Array2&lt;N&gt;,受一些特征的限制。我想这意味着输出类型是通用的,但原始结构 impl 也是如此。我的特质如何不受限制?

【问题讨论】:

  • 有一百万个问题——比如什么?链接到他们并解释您对这些答案的什么。否则,任何回答者都可能只给你完全相同的答案你已经读过,浪费你的时间。
  • 对于不了解极坐标的任何人,也可以独立于该库提出同样的问题:Playground(PhantomData 只是为了让编译器对该结构不使用其通用参数保持沉默)

标签: rust


【解决方案1】:

问题在于 trait 实现对所有 Array2&lt;N::Native&gt; 都是通用的,而 N::Native 是一个关联类型,而不仅仅是参数本身。

可能存在多个不同的 trait PolarsNumericType 实现,它们都具有相同的 Native 类型。因此,那些 Ns 的 trait 实现将具有相同的 Self 类型,并且应该使用哪一个是模棱两可的。

让我用一个独立的玩具例子来说明冲突:

trait TypedNumber {
    type Raw: Copy;
    fn format_raw(x: Self::Raw) -> String;
}

struct Dollars(i64);
impl TypedNumber for Dollars {
    type Raw = i64;
    fn format_raw(x: i64) -> String {
        format!("${}.{:02}\n", x / 100, x % 100)
    }
}

struct Time(i64);
impl TypedNumber for Time {
    type Raw = i64;
    fn format_raw(x: i64) -> String {
        format!("{}m{:02}s\n", x / 60, x % 60)
    }
}

struct Report(String);
impl<N: TypedNumber> Into<Report> for &[N::Raw] {
    fn into(self) -> Report {
        Report(self.into_iter().copied().map(N::format_raw).collect())
    }
}

fn main() {
    let numbers: Vec<i64> = vec![1, 2, 3];
    let report: Report = (&numbers).into();
    
    // Should this print money or time?
    println!("{}", report.0);
}

这里TypedNumber对应你的PolarsNumericType,我的N::Raw对应你的N::Native。使用这个 trait 和 Into impl,我们可以尝试使用 trait 执行操作,但是应该使用 trait 的哪个实现是不明确的。

另一方面,如果您将转换编写为函数(无论是否在 impl DataFrame 内部,N 都将是函数的参数,因此调用者可以(并且必须, 在这种情况下)在调用处指定它,程序可以编译:

fn report<N: TypedNumber>(data: &[N::Raw]) -> String {
    data.into_iter().copied().map(N::format_raw).collect()
}

fn main() {
    let numbers: Vec<i64> = vec![1, 2, 3];
    println!("{}", report::<Dollars>(&numbers));
    println!("{}", report::<Time>(&numbers));
}

为避免此问题,您需要引入一些机会以指定N。不幸的是,我不认为有一个好的方法可以做到这一点——你能做的最多就是引入某种包装类型,它有N 作为类型参数,但是那么你仍然需要指定N 以某种方式,因此您最终会编写像ArrayConvert::&lt;N&gt;::from(data_frame).into() 这样的两步转换,它比现有的to_ndarray&lt;N&gt;() 函数不太整洁。

但是,我对 polars 不是特别熟悉,因此该库的某些细节可能会提供更好的解决方案。

【讨论】:

  • 非常有帮助的答案,我真的没有意识到相关类型导致了这里的问题。但是,我实际上尝试使用 turbofish 运算符,但没有帮助。我的 impl 甚至不会按原样编译,问题不是函数的使用。
  • 但我也想知道您是否可以提供解决方案,因为我不清楚如何清除此限制
  • @Migwell 我已经在我的回答中添加了一些讨论。我认为没有比to_ndarray 有所改进的解决方案。
  • 谢谢,这有点帮助。但我在 OP 中的另一个问题是,为什么结构 impl 允许这样做,而不是我的特征 impl 允许这样做。我还是不明白。
  • 重读您的解释后,我想我现在明白了。如果我们想将DataFrame 转换为Array&lt;f64&gt;,我们需要调用.to_ndarray::&lt;N&gt;,但是有多个N 我们可以选择进行这种转换,这将产生一个f64 数组。所以它是不受约束的。
猜你喜欢
  • 2021-04-11
  • 1970-01-01
  • 2020-10-15
  • 1970-01-01
  • 2022-11-02
  • 1970-01-01
  • 2011-02-27
  • 1970-01-01
  • 2018-07-10
相关资源
最近更新 更多