/ / Як я реалізую загальні комутативні std :: ops за участю вбудованого типу для об'єктів ознаки? - дженерики, іржа, перевантаження оператора, риси

Як реалізувати загальний комутативний 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}`

я б хотів 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
}