简体   繁体   中英

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

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 Hilbert s, 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?

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM