¿Cómo necesito que un tipo genérico implemente una operación como Add, Sub, Mul o Div en una función genérica?

Resuelto Maxim Sloyko asked hace 9 años • 2 respuestas

Estoy intentando implementar una función genérica en Rust donde el único requisito para el argumento es que se defina la operación de multiplicación. Estoy intentando implementar un "poder" genérico, pero utilizaré una cubefunción más simple para ilustrar el problema:

use std::ops::Mul;

fn cube<T: Mul>(x: T) -> T {
    x * x * x
}

fn main() {
    println!("5^3 = {}", cube(5));
}

Al compilar me sale este error:

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`

¿Qué quiere decir esto? ¿Elegí el rasgo equivocado? ¿Cómo puedo resolver esto?

Maxim Sloyko avatar Mar 21 '15 22:03 Maxim Sloyko
Aceptado

Analicemos un poco su ejemplo:

fn cube<T: Mul>(x: T) -> T {
    let a = x * x;
    let b = a * x;
    b
}

¿Cuales son los tipos de ay b? En este caso, el tipo aes <T as std::ops::Mul>::Output: ¿le suena familiar por el mensaje de error? Luego, estamos intentando multiplicar ese tipo nuevamente x, ¡pero no hay garantía de que Outputpueda multiplicarse por algo!

Hagamos lo más simple y digamos que T * Tdebe resultar en T:

fn cube<T: Mul<Output = T>>(x: T) -> T {
    x * x * x
}

Desafortunadamente, esto genera dos errores similares:

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

Lo cual se debe a que el Mulrasgo toma argumentos por valor , por lo que agregamos Copypara poder duplicar los valores.

También cambié a la wherecláusula porque me gusta más y es difícil de manejar tener tanto en línea:

fn cube<T>(x: T) -> T
where
    T: Mul<Output = T> + Copy
{
    x * x * x
}

Ver también:

  • ¿Cómo implemento el rasgo Agregar para una referencia a una estructura?
  • ¿Cómo escribir un rasgo destinado a agregar dos referencias de un tipo genérico?
Shepmaster avatar Mar 21 '2015 15:03 Shepmaster

El límite T: Mulno implica que el resultado del operador binario también sea de tipo T. El tipo de resultado es un tipo asociado de este rasgo: Output.

El otro problema es que antes de Rust 1.0 los rasgos del operador cambiaban de paso por referencia a paso por valor. En código genérico, esto puede ser un poco molesto (al menos por ahora) porque estos operadores consumen sus operandos a menos que también requiera que los tipos sean Copy.

Solo para completar (en caso de que no desee solicitar Copy), permítame agregar información sobre una posible dirección alternativa.

Por el bien del código genérico, se recomienda a los autores de "tipos numéricos" que proporcionen implementaciones adicionales que no consuman estos rasgos de operador para que no necesite Copyo Clone. Por ejemplo, la biblioteca estándar ya proporciona las siguientes implementaciones:

 f64 implements Mul< f64>
 f64 implements Mul<&f64>
&f64 implements Mul< f64>
&f64 implements Mul<&f64>

Cada una de estas implementaciones tiene f64como Outputtipo. Usar estos rasgos directamente no es agradable:

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
}

Con el tiempo, podríamos obtener algunos rasgos de nivel (ligeramente) más altos, lo que reduciría el ruido. Por ejemplo: T: Mul2podría implicar T: Mul<T> + Mul<&T>y &T: Mul<T> + Mul<&T>, pero al momento de escribir esto, el compilador de Rust no parece capaz de manejar esto. Al menos no pude compilar exitosamente el siguiente código:

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)
}

Creo que es seguro decir que las cosas mejorarán en esta área. Por ahora, la capacidad de implementar genéricamente algoritmos numéricos en Rust no es tan buena como me gustaría.

sellibitze avatar Mar 21 '2015 22:03 sellibitze