Я маю:
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}`
я б хотів H * a
дорівнювати a * H
для усіх Hilbert
с H
. У думці іншої відповіді я б спробував:
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 | | }
| |_^
Чому це заборонено? Який правильний спосіб вказати комутативне множення для ознаки ознаки?
Відповіді:
1 для відповіді № 1Чому це заборонено?
Rust застосовує політику, згідно з якою реалізація повинна бути визначена в тому самому ящику, що або ознака, або тип. Ні того, ні іншого Mul
ні f64
знаходяться у вашому ящику.
Це запобігає двозначності щодо якихвпровадження буде використано. Це полегшує компілятору домогтися того, щоб існувало щонайменше один екземпляр ознаки для кожного типу, оскільки він повинен лише перевірити реалізації в цих ящиках. Якби будь-який інший ящик міг визначати екземпляри, тоді компілятору довелося б шукати скрізь. Але також людина, яка намагається міркувати прокоду, потрібно було б знати кожен ящик, щоб здогадатися, яка реалізація в кінцевому підсумку буде використана. Реалізації ознак не називаються елементами в Rust, тому ви навіть не можете про це явно сказати. Ось деяка довідка
Типовим рішенням є використання типу обгортки. Існує нульова вартість виконання, але це зробить API трохи громіздкішим.
Ви також можете визначити власну числову ознаку, яка просто передбачає все Add
, Mul
тощо, впровадити це для всіх примітивних типів і використовувати його як прив’язаний Hilbert
замість усіх окремих рис.
Але це буде брудно, яким би шляхом ви не пішли. І я б поставив під сумнів переваги використання того самого оператора для скалярів, скалярів та змішаних. Це було б далеко простіше просто додати новий метод до вашого API:
fn scale(self, by: f64) -> Self;
Окрім того, що не потрапити в складний безладз усіма цими ознаками та обхідними шляхами намір коду стає набагато зрозумілішим. Вам не доведеться розглядати типи кожної змінної, щоб відрізнити це від множення двох скалярів.
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
}