Как мне требовать, чтобы универсальный тип реализовывал такие операции, как 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 не так хороша, как хотелось бы.

Другие вопросы по тегам