How do I specify the expected result of a `std::ops::Mul` in a trait bound?

守給你的承諾、 提交于 2021-02-10 18:01:35

问题


I have:

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

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

fn g<T: Hilbert>(x: T) -> f64 {
    return (x * 2.0).dot(x);
}

...which yields:

error[E0599]: no method named `dot` found for type `<T as std::ops::Mul<f64>>::Output` in the current scope
 --> src/main.rs:9:22
  |
9 |     return (x * 2.0).dot(x);
  |                      ^^^
  |
  = help: items from traits can only be used if the trait is implemented and in scope
  = note: the following trait defines an item `dot`, perhaps you need to implement it:
          candidate #1: `Hilbert`

I interpret this to mean that Rust can't guarantee that the type T, which has trait Hilbert, has an implementation of std::ops::Mul whose ::Output type is equal to T (a Hilbert).

But I know (and / or wish to demand) that this is the case for all Hilberts, so that functions like g() are possible to write.

I would think to impl std::ops::Mul::Output for Hilbert:

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

...but this has the simultaneous problems that (a) I can't "partially implement" a trait, and would be forced to produce a generic implementation of the function Mul::mul() for all Hilberts, but the actual implementation of Mul::mul() will depend on the specific implementation of Hilbert; and (b) it seems I am not allowed to write this trait at all:

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:12:1
   |
12 | / impl<T: Hilbert> Mul<f64> for T {
13 | |     type Output = T;
14 | | }
   | |_^

How do I persuade Rust that Hilbert * f64 -> Hilbert must hold?


回答1:


How do I persuade Rust that Hilbert * f64 -> Hilbert must hold?

You add a trait bound <T as Mul<f64>>::Output: Hilbert. However, doing so will reveal further issues in your design:

  1. Hilbert.dot() takes the second argument as reference, not by value. But changing the relevant line to (x * 2.0).dot(&x) leads to another error: "expected associated type, found type parameter".
  2. This one is because you defined dot to take Self, but there could be different implementations of Hilbert you want to multiply. dot needs to be generic: fn dot<H: Hilbert>(&self, other: &H) -> f64;
  3. Finally, the borrow checker hits: (x * 2.0).dot(&x) won't let you use x twice, because mul takes its argument by value. You will either have to add a bound Mul<&'a Self> to be able to pass in a reference (which infects your API with lifetime parameters) or make x cloneable (I don't think copyable would apply).

Applying all of the above results in this working(?) compilable code:

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

fn g<T: Hilbert + Clone>(x: T) -> f64
where
    <T as Mul<f64>>::Output: Hilbert,
{
    (x.clone() * 2.0).dot(&x)
}

If Hilbert.dot should not be generic because different implementations of Hilbert do not need to interact, the code can be slightly simpler (in terms of trait bounds):

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

fn g<T: Hilbert + Clone>(x: T) -> f64 {
    (x.clone() * 2.0).dot(&x)
}

However, from what I know about the Hilbert transform, this latter case seems unlikely to be useful.



来源:https://stackoverflow.com/questions/49973888/how-do-i-specify-the-expected-result-of-a-stdopsmul-in-a-trait-bound

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!