variable una tipos son sentencias salida que programacion las entrada ejemplos datos cuáles generics operator-overloading rust

generics - una - ¿Por qué los operadores de Rust tienen el tipo de variable de salida?



tipos de variables en programacion (2)

Esta pregunta ya tiene una respuesta aquí:

He estado leyendo el libro de Rust en línea y he llegado a 4.1, Operadores y sobrecarga . Noté que std::ops::Add define como fn add(self, rhs: RHS) -> Self::Output; con un type Output definida por separado en el rasgo.

Entiendo lo que está sucediendo aquí: la función de add acepta el lado izquierdo como self , y el lado derecho como el parámetro genérico tipo RHS .

Quiero saber por qué el tipo de salida se define con el alias de Output , en lugar de ser otro genérico (por ejemplo, Add<RHS, Output> )? ¿Es solo una convención, o hay una razón específica para ello?


Se trata de quién llega a especificar el tipo de salida. En general, cuando multiplicas dos tipos, digamos f64 por f64 , está claro el tipo de resultado que debería ser: f64 . No tendría sentido que la persona que llama solicite un tipo de resultado de String , por lo que no tiene sentido como entrada.

Me puedo imaginar algunos casos en los que es posible que desee la opción de salida diferente, pero son un nicho poco (por ejemplo, u32 * u32 devolver el resultado u64 completo?); Además, si hubiera más de una opción, ¿cómo se especifica cuál quiere? Si bien podrías escribir Add<u32, u64>::add(a, b) , las expresiones simples como a*b*c serían ambiguas y harían la inferencia del tipo mucho más difícil.


Mientras que las funciones operan en variables, puede pensar en rasgos como funciones que operan en tipos. Cuando se piensa de esta manera, los parámetros tipo sirven como entradas para la función y los tipos asociados sirven como salidas.

Como Output es un tipo asociado, para dos tipos A y B que deseamos impl Add , estamos limitados a elegir un solo tipo de Output . Si Output fuera un parámetro de tipo, podríamos impl Add para A y B en una miríada de formas.

Por ejemplo, definamos un rasgo Mul donde Output es un parámetro:

trait Mul<RHS, Output> { fn mul(self, rhs: RHS) -> Output; }

Ahora definamos un tipo Complex :

#[derive(Debug, Clone, Copy)] struct Complex { x: f64, y: f64, } impl Complex { fn new(x: f64, y: f64) -> Complex { Complex { x: x, y: y } } }

Queremos poder multiplicarlo por f64 :

impl Mul<f64, Complex> for Complex { fn mul(self, rhs: f64) -> Complex { Complex::new(self.x * rhs, self.y * rhs) } }

Todo esto funciona bien Sin embargo, podríamos llegar a una segunda implementación:

impl Mul<f64, f64> for Complex { fn mul(self, rhs: f64) -> f64 { self.x * rhs } }

Cuando multiplicamos un Complex por un f64 ahora, es ambiguo a qué implementación debe llamarse, y la persona que llama necesita información de tipo adicional. Al hacer que Output un tipo asociado, esto no se permite. El siguiente código arroja un error de compilación para implementaciones conflictivas:

impl std::ops::Mul<f64> for Complex { type Output = Complex; fn mul(self, rhs: f64) -> Complex { Complex::new(self.x * rhs, self.y * rhs) } } impl std::ops::Mul<f64> for Complex { type Output = f64; fn mul(self, rhs: f64) -> Complex { self.x * rhs } }

Ejemplo completo