2015-07-16 24 views
9

Nei linguaggi orientati agli oggetti tradizionali (ad esempio Java), è possibile "estendere" la funzionalità di un metodo in una classe ereditata chiamando il metodo originale dalla super classe nella versione sostituita, ad esempio:È possibile estendere un'implementazione di metodo predefinita di un tratto in una struttura?

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!"); 
    } 
} 

Come potete vedere, in Java, sono in grado di chiamare la versione originale della super classe usando la parola chiave super. Sono stato in grado di ottenere il comportamento equivalente per i tratti ereditati, ma non quando si implementano i tratti per le strutture.

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

Così, in caso di tratti ereditari, chiamando l'implementazione di default originale non è un problema, tuttavia, utilizzando la stessa sintassi in sede di attuazione struct presenta un comportamento diverso. È un problema all'interno di Rust? C'è un modo per aggirarlo? O mi manca qualcosa?

risposta

9

Questo non è possibile ora direttamente.

Tuttavia, RFC 1210: impl specialization contiene vari aspetti che renderanno questo tipo di lavoro comportamenti, per esempio, qualcosa come questo dovrebbe funzionare:

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

Fare una chiamata super è esplicitamente menzionato come an extension ad esso e così ha vinto' t appare necessariamente immediatamente, ma potrebbe apparire in futuro.

Nel frattempo, l'approccio generalmente utilizzato è quello di definire una funzione separata per il comportamento predefinito e chiamare che nel metodo predefinito, e quindi gli utenti possono emulare la chiamata super::... semplicemente chiamando direttamente che funzione:

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

Detto questo, Rust preferisce la composizione all'ereditarietà: i progetti che funzionano bene in Java non possono e non devono essere forzati 1-a-1 in Rust.

Foo::method(self); // this apparently calls this overridden 
         // version, because it overflows the stack 

La sintassi percorso qualificato, è Trait::method(value) zucchero per <Type as Trait>::method(value) dove Type è il tipo di value (o, eventualmente, del tipo dopo dereferencing un numero di volte). Cioè, chiama il metodo sul tipo specifico come l'hai scoperto.

+0

Ok, penso di aver capito, quello che ancora mi infastidisce è perché funziona per i tratti ereditari? La stessa sintassi 'Foo :: method (self)' che chiama il metodo sul tipo specifico quando chiamato all'interno dell'implementazione del tratto per una struttura richiama l'implementazione originale quando viene chiamata all'interno di una caratteristica ereditata. Perchè è questo? – faiface

+3

Non sta facendo quello che ti aspetti. Stai definendo un nuovo metodo 'Boo :: method' diverso da quello' Foo :: method'. Sta ancora chiamando il metodo specifico sul tipo; Immagino che il tuo codice di test non abbia sovrascritto 'method' su' Foo', solo su 'Bar'. – huon

+0

Sì, grazie, hai ragione, ora lo capisco molto meglio. – faiface

3

Si tratta di un problema all'interno di Rust?

No, questo sta funzionando come previsto

Esiste un modo intorno ad esso?

È possibile spostare il metodo su una funzione libera e quindi chiamarlo direttamente, una volta dal metodo predefinito e una volta dal metodo "sovrascritto".

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 sono solo perdendo qualcosa?

ruggine non è un linguaggio orientato agli oggetti, ruggine può essere un linguaggio orientato agli oggetti, ma non tutti i linguaggi OO sono creati uguali. La ruggine potrebbe non adattarsi perfettamente ai paradigmi tradizionali che ti aspetti.

Vale a dire, i tratti non esistono in fase di esecuzione. Solo quando vengono applicati e utilizzati con una struct viene generato il codice che è richiamabile. Quando crei la tua implementazione di un metodo, questo sostituisce l'implementazione predefinita; non esiste da nessuna parte l'implementazione del metodo di default.

Spesso il tuo codice può essere scritto in modo diverso. Forse il codice veramente condiviso dovrebbe essere estratto come metodo su una nuova struttura, o forse si fornisce una chiusura a un metodo per personalizzare il comportamento.

+0

Grazie per la risposta. Lo sto chiedendo perché questo paradigma di estendere la funzionalità delle classi esistenti è spesso e abbastanza efficacemente utilizzato nello sviluppo rapido di applicazioni. A volte risulta in un codice piuttosto brutto, ma spesso è molto efficace, quindi mi chiedevo se riuscissi a ottenere un equivalente in Rust. Estrarre la funzionalità in una funzione separata non mi sembra molto utile, perché devi pensare in anticipo quando lo fai. Una volta che l'API è già stata stabilita, non puoi estrarre nulla e allora? Ma ovviamente segnerò la tua risposta corretta se non appare nulla di meglio. – faiface

+0

@faiface * perché devi pensare in anticipo * - Penso che continuerò a pubblicizzarlo come un vantaggio di Rust, non un rovescio della medaglia^_^ – Shepmaster

+1

@Shepmaster: "Rust non è un linguaggio orientato agli oggetti." è completamente falso. Rust non usa l'OOP completo * basato sulla classe, ma è * sicuramente * un linguaggio orientato agli oggetti. (Perché, anche l'articolo di Wikipedia dice così!) –