Как мне требовать, чтобы универсальный тип реализовывал такие операции, как Add, Sub, Mul или Div, в универсальной функции?
Я пытаюсь реализовать универсальную функцию в Rust, где единственным требованием для аргумента является определение операции умножения. Я пытаюсь реализовать общую "власть", но пойдет с более простым cube
Функция для иллюстрации проблемы:
use std::ops::Mul;
fn cube<T: Mul>(x: T) -> T {
x * x * x
}
fn main() {
println!("5^3 = {}", cube(5));
}
При компиляции я получаю эту ошибку:
error[E0369]: binary operation `*` cannot be applied to type `<T as std::ops::Mul>::Output`
--> src/main.rs:4:5
|
4 | x * x * x
| ^^^^^^^^^
|
= note: an implementation of `std::ops::Mul` might be missing for `<T as std::ops::Mul>::Output`
Что это значит? Я выбрал неправильную черту? Как я могу решить это?
2 ответа
Давайте разберем ваш пример немного:
fn cube<T: Mul>(x: T) -> T {
let a = x * x;
let b = a * x;
b
}
Какие типы a
а также b
? В этом случае тип a
является <T as std::ops::Mul>::Output
- звучит знакомо из сообщения об ошибке? Затем мы пытаемся умножить этот тип на x
еще раз, но нет никакой гарантии, что Output
умеет умножаться на что угодно!
Давайте сделаем самое простое и скажем, что T * T
должен привести к T
:
fn cube<T: Mul<Output = T>>(x: T) -> T {
x * x * x
}
К сожалению, это дает две похожие ошибки:
error[E0382]: use of moved value: `x`
--> src/lib.rs:6:9
|
6 | x * x * x
| - ^ value used here after move
| |
| value moved here
|
= note: move occurs because `x` has type `T`, which does not implement the `Copy` trait
Это потому, что Mul
trait принимает аргументы по значению, поэтому мы добавляем Copy
поэтому мы можем продублировать значения.
Я также перешел на where
пункт, как мне нравится больше, и это громоздко, чтобы иметь так много встроенных:
fn cube<T>(x: T) -> T
where
T: Mul<Output = T> + Copy
{
x * x * x
}
Связанный T: Mul
не означает, что результат бинарного оператора также имеет тип T
, Тип результата - это связанный тип этой черты: Output
,
Другая проблема заключается в том, что в определенный момент характеристики оператора переключаются с передачи по ссылке на передачу по значению. В общем коде это может быть немного неприятно (пока, по крайней мере), потому что эти операторы используют свои операнды, если только вы не требуете, чтобы типы были Copy
,
Просто для полноты (на случай, если вам не понравится Copy
), позвольте мне добавить некоторую информацию о возможном альтернативном направлении.
Ради общего кода авторам "числовых типов" предлагается предоставить дополнительные необработанные реализации этих признаков оператора, чтобы вам не нужно Copy
или же Clone
, Например, стандартная библиотека уже предоставляет следующие реализации:
f64 implements Mul< f64>
f64 implements Mul<&f64>
&f64 implements Mul< f64>
&f64 implements Mul<&f64>
с каждой реализацией, имеющей f64
как Output
тип. Но использование этих черт напрямую не очень приятно:
fn cube<T>(x: &T) -> T
where for<'a> T: Mul<&'a T, Output = T>,
for<'a,'b> &'a T: Mul<&'b T, Output = T>
{
x * x * x
}
В конце концов, мы можем получить некоторые (немного) более высокие характеристики уровня, которые уменьшат шум. Например: T: Mul2
может подразумевать T: Mul<T> + Mul<&T>
а также &T: Mul<T> + Mul<&T>
, Но на момент написания этой статьи компилятор Rust, похоже, не справился с этим. По крайней мере, я не смог успешно скомпилировать следующий код:
use std::ops::Mul;
pub trait Mul2 where
Self: Mul<Self, Output=Self>,
Self: for<'a> Mul<&'a Self, Output=Self>,
for<'a> &'a Self: Mul<Self, Output=Self>,
for<'a,'b> &'a Self: Mul<&'b Self, Output=Self> {}
impl<T> Mul2 for T where
T: Mul<T, Output=T>,
T: for<'a> Mul<&'a T, Output=T>,
for<'a> &'a T: Mul<T, Output=T>,
for<'a,'b> &'a T: Mul<&'b T, Output=T> {}
fn cube<T: Mul2>(x: &T) -> T {
x * x * x
}
fn main() {
let c = cube(&2.3);
println!("Hello, world! {}", c)
}
Я думаю, можно с уверенностью сказать, что ситуация в этой области улучшится. На данный момент способность в общем случае реализовывать числовые алгоритмы в Rust не так хороша, как хотелось бы.