J'ai un trait appelé Sleep
:
pub trait Sleep {
fn sleep(&self);
}
Je pourrais fournir une implémentation différente de sleep pour chaque structure, mais il s'avère que la plupart des gens dorment de très petites façons. Vous pouvez dormir dans un lit:
pub trait HasBed {
fn sleep_in_bed(&self);
fn jump_on_bed(&self);
}
impl Sleep for HasBed {
fn sleep(&self) {
self.sleep_in_bed()
}
}
Si vous campez, vous pouvez dormir dans une tente:
pub trait HasTent {
fn sleep_in_tent(&self);
fn hide_in_tent(&self);
}
impl Sleep for HasTent {
fn sleep(&self) {
self.sleep_in_tent()
}
}
Il y a des cas bizarres. J'ai un ami qui peut dormir debout contre un mur, mais la plupart des gens, la plupart du temps, tombent dans un cas simple.
Nous définissons quelques structures et les laissons dormir:
struct Jim;
impl HasBed for Jim {
fn sleep_in_bed(&self) {}
fn jump_on_bed(&self) {}
}
struct Jane;
impl HasTent for Jane {
fn sleep_in_tent(&self) {}
fn hide_in_tent(&self) {}
}
fn main() {
use Sleep;
let jim = Jim;
jim.sleep();
let jane = Jane;
jane.sleep();
}
Oh-oh! Erreur de compilation:
error[E0599]: no method named `sleep` found for type `Jim` in the current scope
--> src/main.rs:44:9
|
27 | struct Jim;
| ----------- method `sleep` not found for this
...
44 | jim.sleep();
| ^^^^^
|
= help: items from traits can only be used if the trait is implemented and in scope
= note: the following trait defines an item `sleep`, perhaps you need to implement it:
candidate #1: `Sleep`
error[E0599]: no method named `sleep` found for type `Jane` in the current scope
--> src/main.rs:47:10
|
34 | struct Jane;
| ------------ method `sleep` not found for this
...
47 | jane.sleep();
| ^^^^^
|
= help: items from traits can only be used if the trait is implemented and in scope
= note: the following trait defines an item `sleep`, perhaps you need to implement it:
candidate #1: `Sleep`
Cette erreur de compilation est étrange car s'il y avait quelque chose de mal avec un trait implémentant un autre trait, je m'attendais à en entendre parler quand je l'ai fait, pas tout en bas du programme lorsque j'essaie d'utiliser le résultat.
Dans cet exemple, il n'y a que 2 structures et 2 façons de dormir, mais dans le cas général, il existe de nombreuses structures et plusieurs façons de dormir (mais pas autant de façons qu'il y a de structures).
Un Bed
est principalement une implémentation de Sleep
, mais dans le cas général un Bed
a de nombreuses utilisations et pourrait implémenter beaucoup de choses.
La seule approche immédiatement évidente consiste à convertir impl Sleep for...
dans une macro qui se structure, mais qui semble hacky et terrible.
Vous devez implémenter le deuxième trait pour les objets qui implémentent le premier trait:
impl<T> Sleep for T
where
T: HasBed,
{
fn sleep(&self) {
self.sleep_in_bed()
}
}
Auparavant, vous implémentiez Sleep
pour le type de trait, mieux exprimé par dyn HasBed
. Voir Que signifie "dyn" dans un type? pour plus de détails.
Cependant, cela va s'arrêter dès que vous ajoutez une deuxième implémentation de couverture:
impl<T> Sleep for T
where
T: HasTent,
{
fn sleep(&self) {
self.sleep_in_tent()
}
}
Avec
error[E0119]: conflicting implementations of trait `Sleep`:
--> src/main.rs:24:1
|
10 | / impl<T> Sleep for T
11 | | where
12 | | T: HasBed,
13 | | {
... |
16 | | }
17 | | }
| |_- first implementation here
...
24 | / impl<T> Sleep for T
25 | | where
26 | | T: HasTent,
27 | | {
... |
30 | | }
31 | | }
| |_^ conflicting implementation
Il est possible que quelque chose implémente à la fois HasBed
et HasTent
. Si quelque chose devait apparaître qui implémentait les deux, le code serait désormais ambigu. La solution de contournement serait spécialisation, mais il n'y a pas encore d'implémentation stable de cela.
Comment atteignez-vous votre objectif? Je pense que vous avez déjà suggéré la meilleure solution actuelle - écrivez une macro. Vous pouvez également écrire votre propre macro dérivée . Les macros ne sont vraiment pas si mauvaises, mais elles peuvent être difficiles à écrire.
Une autre chose, qui peut être entièrement basée sur les noms que vous avez choisis pour votre exemple, serait d'incorporer simplement des structures dans d'autres structures, en les rendant éventuellement publiques. Puisque votre implémentation de Sleep
ne dépend essentiellement que du lit/de la tente, aucune fonctionnalité ne serait perdue en faisant cela. Bien sûr, certaines personnes peuvent penser que cela rompt l'encapsulation. Vous pouvez à nouveau créer des macros pour implémenter une sorte de délégation.
trait Sleep {
fn sleep(&self);
}
struct Bed;
impl Bed {
fn jump(&self) {}
}
impl Sleep for Bed {
fn sleep(&self) {}
}
struct Tent;
impl Tent {
fn hide(&self) {}
}
impl Sleep for Tent {
fn sleep(&self) {}
}
struct Jim {
bed: Bed,
}
struct Jane {
tent: Tent,
}
fn main() {
let jim = Jim { bed: Bed };
jim.bed.sleep();
}
Nous pouvons utiliser des éléments associés ici.
pub trait Sleep: Sized {
type Env: SleepEnv;
fn sleep(&self, env: &Self::Env) {
env.do_sleep(self);
}
fn get_name(&self) -> &'static str;
}
pub trait SleepEnv {
fn do_sleep<T: Sleep>(&self, &T);
}
Ensuite, nous implémentons deux environnements de sommeil différents.
struct Bed;
struct Tent;
impl SleepEnv for Bed {
fn do_sleep<T: Sleep>(&self, person: &T) {
println!("{} is sleeping in bed", person.get_name());
}
}
impl SleepEnv for Tent {
fn do_sleep<T: Sleep>(&self, person: &T) {
println!("{} is sleeping in tent", person.get_name());
}
}
La dernière pièce est leur implémentation concrète.
struct Jim;
struct Jane;
impl Sleep for Jim {
type Env = Bed;
fn get_name(&self) -> &'static str {
"Jim"
}
}
impl Sleep for Jane {
type Env = Tent;
fn get_name(&self) -> &'static str {
"Jane"
}
}
Code de test:
fn main() {
let bed = Bed;
let tent = Tent;
let jim = Jim;
let jane = Jane;
jim.sleep(&bed);
jane.sleep(&tent);
}