oop - temperamento - teoria de los rasgos liderazgo
¿Es posible extender una implementación de método predeterminada de un rasgo en una estructura? (2)
En los lenguajes orientados a objetos tradicionales (por ejemplo, Java), es posible "extender" la funcionalidad de un método en una clase heredada llamando al método original desde la superclase en la versión reemplazada, por ejemplo:
class A {
public void method() {
System.out.println("I am doing some serious stuff!");
}
}
class B extends A {
@Override
public void method() {
super.method(); // here we call the original version
System.out.println("And I''m doing something more!");
}
}
Como puede ver, en Java, puedo llamar a la versión original de la superclase con la palabra clave super
. Pude obtener el comportamiento equivalente para los rasgos heredados, pero no al implementar los rasgos para las estructuras.
trait Foo {
fn method(&self) {
println!("default implementation");
}
}
trait Boo: Foo {
fn method(&self) {
// this is overriding the default implementation
Foo::method(self); // here, we successfully call the original
// this is tested to work properly
println!("I am doing something more.");
}
}
struct Bar;
impl Foo for Bar {
fn method(&self) {
// this is overriding the default implementation as well
Foo::method(self); // this apparently calls this overridden
// version, because it overflows the stack
println!("Hey, I''m doing something entirely different!");
println!("Actually, I never get to this point, ''cause I crash.");
}
}
fn main() {
let b = Bar;
b.method(); // results in "thread ''<main>'' has overflowed its stack"
}
Entonces, en el caso de rasgos heredados, llamar a la implementación predeterminada original no es un problema, sin embargo, usar la misma sintaxis al implementar estructuras muestra un comportamiento diferente. ¿Es esto un problema dentro de Rust? ¿Hay alguna forma de evitarlo? ¿O solo me estoy perdiendo algo?
¿Es esto un problema dentro de Rust?
No, esto está funcionando como estaba previsto
¿Hay alguna forma de evitarlo?
Puede mover el método a una función gratuita y luego llamarlo directamente, una vez desde el método predeterminado y una vez desde el método "anulado".
fn the_default() {
println!("default implementation");
}
trait Foo {
fn method(&self) {
the_default()
}
}
struct Bar;
impl Foo for Bar {
fn method(&self) {
the_default();
println!("Hey, I''m doing something entirely different!");
}
}
fn main() {
let b = Bar;
b.method();
}
¿O solo me estoy perdiendo algo?
Rust no es un lenguaje orientado a objetos, Rust puede ser un lenguaje orientado a objetos, pero no todos los lenguajes OO se crean de la misma manera. Es posible que Rust no encaje en los paradigmas tradicionales que esperas.
A saber, los rasgos no existen en tiempo de ejecución. Solo cuando se aplican ay se utilizan con una estructura se genera un código que es invocable. Cuando crea su propia implementación de un método, reemplaza la implementación predeterminada; no hay lugar para que exista la implementación del método predeterminado.
Muchas veces, su código se puede escribir de una manera diferente. Tal vez el código verdaderamente compartido se debe extraer como un método en una nueva estructura, o tal vez proporcione un cierre a un método para personalizar el comportamiento.
Esto no es posible directamente ahora.
Sin embargo, la especialización RFC 1210: impl
contiene varios aspectos que harán que este tipo de comportamiento funcione, por ejemplo, algo como esto debería funcionar:
trait Foo {
fn method(&self) { println!("default implementation"); }
}
trait Bar: Foo { ... }
partial impl<T: Bar> Foo for T {
default fn method(&self) { println!("Bar default"); }
}
Hacer una super
llamada se menciona explícitamente como una extensión de la misma y, por lo tanto, no necesariamente aparecerá inmediatamente, pero puede aparecer en el futuro.
Mientras tanto, el enfoque generalmente utilizado es definir una función separada para el comportamiento predeterminado y llamar a eso en el método predeterminado, y luego los usuarios pueden emular la llamada super::...
simplemente llamando a esa función directamente:
trait Foo {
fn method(&self) { do_method(self) }
}
fn do_method<T: Foo>(_x: &T) {
println!("default implementation");
}
impl Foo for Bar {
fn method(&self) {
do_method(self);
println!("more");
}
}
Dicho esto, Rust prefiere la composición por sobre la herencia: los diseños que funcionan bien en Java no pueden y no deben ser forzados de 1 a 1 en Rust.
Foo::method(self); // this apparently calls this overridden // version, because it overflows the stack
La sintaxis de ruta calificada, Trait::method(value)
es sugar para <Type as Trait>::method(value)
donde Type
es el tipo de value
(o, posiblemente, el tipo después de eliminar referencias varias veces). Es decir, está llamando al método en el tipo específico como descubrió.