generics - ejemplos - title html attribute
¿Cómo requiero que un tipo genérico implemente una operación como Agregar, Sub, Mul o Div en una función genérica? (2)
Analicemos un poco su ejemplo:
fn cube<T: Mul>(x: T) -> T {
let a = x * x;
let b = a * x;
b
}
¿Cuáles son los tipos de
b
?
En este caso, el tipo de
a
es
<T as std::ops::Mul>::Output
- ¿le suena familiar el mensaje de error?
Entonces, estamos tratando de multiplicar ese tipo por
x
nuevamente, ¡pero no hay garantía de que
Output
pueda multiplicarse por nada!
Hagamos lo más simple y digamos que
T * T
necesita dar como resultado una
T
:
fn cube<T: Mul<Output = T>>(x: T) -> T {
x * x * x
}
Desafortunadamente, esto da 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
rasgo
Mul
toma argumentos por valor
, por lo que agregamos la
Copy
para poder duplicar los valores.
También cambié a la cláusula
where
ya que me gusta más y es difícil 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 limitado para agregar dos referencias de un tipo genérico?
Estoy tratando de implementar una función genérica en Rust donde el único requisito para el argumento es que la operación de multiplicación debe definirse.
Estoy tratando de implementar un "poder" genérico, pero iré con una función de
cube
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é significa esto? ¿Elegí el rasgo equivocado? ¿Cómo puedo resolver esto?
El límite
T: Mul
no implica que el resultado del operador binario sea también del 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 cambiaron de paso por referencia a paso por valor.
En el 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 una
Copy
), permítame agregar información sobre una posible dirección alternativa.
Por el bien del código genérico, se alienta a los autores de "tipos numéricos" a proporcionar implementaciones adicionales
no consumidoras
de estos rasgos de operador para que no necesite
Copy
o
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
f64
como tipo de
Output
.
Hacer uso de estos rasgos directamente no es bonito:
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
}
Eventualmente, podríamos obtener algunos rasgos (ligeramente) de mayor nivel, lo que reduciría el ruido.
Por ejemplo:
T: Mul2
podría implicar
T: Mul<T> + Mul<&T>
y
&T: Mul<T> + Mul<&T>
, pero al momento de escribir esto, el compilador Rust no parece capaz de manejar esto .
Al menos no pude compilar con éxito 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 que fuera.