如何实现涉及特征对象的内置类型的通用交换性std :: ops? [英] How do I implement generic commutative std::ops involving a builtin type for trait objects?

查看:84
本文介绍了如何实现涉及特征对象的内置类型的通用交换性std :: ops?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有:

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

pub trait Hilbert:
    Add + Sub + Mul + Div + Neg + Mul<f64, Output = Self> + Div<f64, Output = Self> + Sized + Copy
{
    fn dot(&self, other: &Self) -> f64;
    fn magnitude(&self) -> f64;
}

fn g<T: Hilbert>(x: T) -> f64 {
    let a = (x * 2.0).dot(&x);
    let b = (2.0 * x).dot(&x);
    a + b
}

error[E0277]: cannot multiply `T` to `{float}`
  --> src/main.rs:12:18
   |
12 |     let b = (2.0 * x).dot(&x);
   |                  ^ no implementation for `{float} * T`
   |
   = help: the trait `std::ops::Mul<T>` is not implemented for `{float}`

我希望所有HilbertHH * a等于a * H.按照另一种答案,我会尝试:

I would like H * a to equal a * H for all Hilberts H. In the vein of another answer, I would try:

impl<T: Hilbert> Mul<T> for f64 {
    type Output = T;

    fn mul(self, other: T) -> T {
        other * self
    }
}

但这会产生:

error[E0210]: type parameter `T` must be used as the type parameter for some local type (e.g. `MyStruct<T>`); only traits defined in the current crate can be implemented for a type parameter
  --> src/main.rs:16:1
   |
16 | / impl<T: Hilbert> Mul<T> for f64 {
17 | |     type Output = T;
18 | |
19 | |     fn mul(self, other: T) -> T {
20 | |         other * self
21 | |     }
22 | | }
   | |_^

为什么不允许这样做?为特征对象指定可交换乘法的正确方法是什么?

Why is this disallowed? What's the proper way to specify commutative multiplication for a trait object?

推荐答案

为什么不允许这样做?

Why is this disallowed?

Rust强制执行一项策略,即必须在与特征或类型相同的板条箱中定义实现.您的板条箱中都没有Mulf64.

Rust enforces a policy that an implementation must be defined in the same crate as either the trait or the type. Neither Mul nor f64 are in your crate.

这避免了使用哪种实现方式的歧义.编译器可以很容易地强制每种类型最多存在一个特征实例,因为它只需要检查那些包装箱中的实现即可.如果有其他板条箱可以定义实例,则编译器将不得不在各处查找 .但是,试图推理代码的人也必须熟悉每个板条箱,才能猜测最终将使用哪种实现.特质实现在Rust中没有命名项目,因此您甚至无法对其进行明确说明. 这里有一些背景

This prevents ambiguity about which implementation is going to be used. It makes it easy for the compiler to enforce that at most one instance of a trait exists per type, since it only has to check the implementations in those crates. If any other crate could define instances then the compiler would have to look everywhere. But also a human, trying to reason about the code, would have to be familiar with every crate, in order to guess which implementation would end up being used. Trait implementations are not named items in Rust, so you couldn't even be explicit about it. Here's some background

常见的解决方法是使用包装器类型.这样做的运行时成本为零,但这会使API更加麻烦.

A common workaround is to use a wrapper type. There's zero runtime cost to doing so, but it will make the API a bit more cumbersome.

您还可以定义自己的数字特征,即暗示所有AddMul等,为所有原始类型实现 并将其用作而不是所有个人特征.

You can also define your own numeric trait, which just implies all of Add, Mul etc, implement that for all the primitive types, and use it as the bound in Hilbert instead of all the individual traits.

但是,无论走哪条路,这都会很混乱.我会质疑对标量,非标量和混合量使用同一运算符的好处.只是将新方法添加到您的API中, far 会更简单:

But this is going to be messy whichever route you go. And I would question the benefit of using the same operator for scalars, non-scalars and mixed. It would be far simpler to just add a new method to your API:

fn scale(self, by: f64) -> Self;

除了不陷入所有这些特征范围和变通办法的复杂混乱之外,代码的意图更加清晰.您无需查看每个变量的类型即可将其与两个标量的乘法相区别.

Apart from not getting into a complicated mess with all those trait bounds and workarounds, the intent of the code is much clearer. You won't have to look at the types of each variable to distinguish this from a multiplication of two scalars.

fn g<T: Hilbert>(x: T) -> f64 {
    let a = x.scale(2.0).dot(&x);
    let b = x.scale(2.0).dot(&x);
    a + b
}

这篇关于如何实现涉及特征对象的内置类型的通用交换性std :: ops?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆