rust - ¿Hay alguna manera de implementar un rasgo sobre otro rasgo?
traits (3)
Esta pregunta ya tiene una respuesta aquí:
Estoy tratando de crear un rasgo base que implemente otros rasgos de operador ( Add
, Subtract
, Multiply
, Divide
, etc ...) para mí.
Esto no se puede compilar, parece que se emitió con Sized
, pero incluso cuando la Measurement
está configurada para exigir Sized
, no funciona. ¿Esto es posible?
use std::ops::Add;
#[derive(Copy, Clone, Debug)]
struct Unit {
value: f64,
}
impl Unit {
fn new(value: f64) -> Unit {
Unit { value: value }
}
}
trait Measurement: Sized {
fn get_value(&self) -> f64;
fn from_value(value: f64) -> Self;
}
impl Measurement for Unit {
fn get_value(&self) -> f64 {
self.value
}
fn from_value(value: f64) -> Self {
Unit::new(value)
}
}
// This explicit implementation works
/*
impl Add for Unit {
type Output = Unit;
fn add(self, rhs: Unit) -> Unit {
let a = self.get_value();
let b = rhs.get_value();
Unit::from_value(a + b)
}
}
*/
// This trait implementation does not
impl Add for Measurement {
type Output = Self;
fn add(self, rhs: Self) -> Self {
let a = self.get_value();
let b = rhs.get_value();
Self::from_value(a + b)
}
}
fn main() {
let a = Unit::new(1.5);
let b = Unit::new(2.0);
let c = a + b;
println!("{}", c.get_value());
}
( patio de recreo )
error[E0277]: the trait bound `Measurement + ''static: std::marker::Sized` is not satisfied
--> src/main.rs:42:6
|
42 | impl Add for Measurement {
| ^^^ `Measurement + ''static` does not have a constant size known at compile-time
|
= help: the trait `std::marker::Sized` is not implemented for `Measurement + ''static`
error[E0038]: the trait `Measurement` cannot be made into an object
--> src/main.rs:42:6
|
42 | impl Add for Measurement {
| ^^^ the trait `Measurement` cannot be made into an object
|
= note: the trait cannot require that `Self : Sized`
error[E0038]: the trait `Measurement` cannot be made into an object
--> src/main.rs:43:5
|
43 | type Output = Self;
| ^^^^^^^^^^^^^^^^^^^ the trait `Measurement` cannot be made into an object
|
= note: the trait cannot require that `Self : Sized`
error[E0038]: the trait `Measurement` cannot be made into an object
--> src/main.rs:45:5
|
45 | fn add(self, rhs: Self) -> Self {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Measurement` cannot be made into an object
|
= note: the trait cannot require that `Self : Sized`
El problema no es con Sized. La sintaxis que estás buscando es:
impl<T: Measurement> Add for T { ... }
en lugar de:
impl Add for Measurement { ... }
Debido a que el lado derecho del for
debe ser un objeto, no un rasgo, sin embargo, un parámetro de tipo restringido a un rasgo (es decir, una T
requerida para ser Measurement
) es válido.
Ahora su código aún no se compilará. Obtendrás lo siguiente:
error: el parámetro de tipo
T
debe usar como parámetro de tipo para algún tipo local (por ejemplo,MyStruct<T>
); solo los rasgos definidos en la caja actual pueden implementarse para un parámetro de tipo [E0210]
El problema aquí es de un tipo totalmente diferente. No estoy seguro de que esté relacionado con la pregunta, pero seguiré explicando lo que está pasando. Cuando escribe una impl para Add
a cualquier T
que es Measurement
, abre la posibilidad de que un tipo ya implemente Add
por sí mismo y también implemente Measurement
otro lugar. Imagínese si quisiera implementar Measurement
on u8
(lo cual es tonto pero posible ): ¿cuál debería elegir Rust para Add
? ¿La std
estándar original o su impl de Measurement
? ( discusión en profundidad sobre este tema )
En este momento, Rust simplemente prohíbe una impl si no es al menos 1) tu propio rasgo o 2) tu propio tipo (donde "propio" significa formalmente, en la caja estás escribiendo tu impl). Esta es la razón por la que puedes escribir impl Add for Unit
: porque tienes Unit
.
La solución más fácil sería renunciar e implementar Agregar de forma independiente para cada tipo que planee hacer de Unit
. Digamos que su caja define Inches
y Centimeter
, cada una tendría su propia adición impl. Si el código es insultantemente similar, y sientes que rompiste DRY, aprovecha macros . Así es como lo hace la caja std
:
macro_rules! add_impl {
($($t:ty)*) => ($(
#[stable(feature = "rust1", since = "1.0.0")]
impl Add for $t {
type Output = $t;
#[inline]
fn add(self, other: $t) -> $t { self + other }
}
forward_ref_binop! { impl Add, add for $t, $t }
)*)
}
No puede implementar un rasgo para un rasgo, implementa un rasgo solo para tipos. Pero puede implementar un rasgo para un tipo genérico que implemente ciertos rasgos (límites de rasgo). Algo como esto:
impl<T : Measurement> Add<T> for T {
type Output = T;
fn add(self, rhs: Self) -> T {
let a = self.get_value();
let b = rhs.get_value();
T::from_value(a + b)
}
}
Desafortunadamente, puedes hacer esto solo para los rasgos definidos en tu caja (se llama coherencia), por lo que no puedes hacer eso para el std Add
Trait porque está definido en la caja estándar, no en la tuya.
Creo que es posible que necesite definir algunas macros para hacer lo que quiere hacer.
Aquí hay una versión funcional con macros, como se sugiere:
use std::ops::Add;
#[derive(Copy, Clone, Debug)]
struct Unit {
value: f64,
}
impl Unit {
fn new(value: f64) -> Unit {
Unit { value: value }
}
}
trait Measurement: Sized {
fn get_value(&self) -> f64;
fn from_value(value: f64) -> Self;
}
impl Measurement for Unit {
fn get_value(&self) -> f64 {
self.value
}
fn from_value(value: f64) -> Self {
Unit::new(value)
}
}
macro_rules! add_impl {
($($t:ty)*) => ($(
impl Add for $t {
type Output = $t;
fn add(self, other: $t) -> $t {
let a = self.get_value();
let b = other.get_value();
let r = a + b;
Self::from_value(r)
}
}
)*)
}
add_impl! { Unit }
fn main() {
let a = Unit::new(1.5);
let b = Unit::new(2.0);
let c = a + b;
println!("{}", c.get_value());
}