J'ai deux traits qui sont fondamentalement équivalents, mais l'un fournit une interface de niveau inférieur à l'autre. Étant donné le trait de niveau supérieur, on peut facilement mettre en œuvre le trait de niveau inférieur. Je veux écrire une bibliothèque qui accepte une implémentation de l'un ou l'autre trait.Trait de rouille avec les versions "simples" et "avancées"
Mon cas particulier est un trait pour traverser un arbre:
// "Lower level" version of the trait
pub trait RawState {
type Cost: std::cmp::Ord + std::ops::Add<Output = Self::Cost> + std::marker::Copy;
type CulledChildrenIterator: Iterator<Item = (Self, Self::Cost)>;
fn cull(&self) -> Option<Self::Cost>;
fn children_with_cull(&self) -> Self::CulledChildrenIterator;
}
// "Higher level" version of the trait
pub trait State: RawState {
type ChildrenIterator: Iterator<Item = (Self, Self::Cost)>;
fn children(&self) -> Self::ChildrenIterator;
}
// Example of how RawState could be implemented using State
fn state_children_with_cull<S: State> (s: S)
-> impl Iterator<Item = (S, S::Cost)>
{
s.children()
.filter_map(move |(state, transition_cost)|
state.cull().map(move |emission_cost|
(state, transition_cost + emission_cost)
)
)
}
Ici, trait État fournit une interface où vous définissez la fonction .children() à la liste des enfants, et la fonction .cull()
potentiellement abattage un état.
Le trait RawState
fournit une interface dans laquelle vous définissez une fonction .children_with_cull()
à la place, qui itère à travers les enfants et les supprime dans un appel de fonction unique. Cela permet à une implémentation de RawState
de ne jamais même générer des enfants dont il sait qu'ils seront éliminés.
Je souhaite autoriser la plupart des utilisateurs à implémenter uniquement le trait State
et à générer automatiquement l'implémentation RawState
en fonction de leur implémentation d'état. Cependant, lors de la mise en œuvre de State
, certaines parties du trait font toujours partie de RawState
, par ex.
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
struct DummyState {}
impl State for DummyState {
type Cost = u32;
type ChildrenIterator = DummyIt;
fn emission(&self) -> Option<Self::Cost> {
Some(0u32)
}
fn children(&self) -> DummyIt {
return DummyIt {};
}
}
Donner des erreurs, car le type "Coût" est défini dans RawState, pas dans State. En solution de contournement potentiel, de redéfinir toutes les parties pertinentes de l'intérieur RawState Etat, à savoir définir Etat
pub trait State: RawState {
type Cost: std::cmp::Ord + std::ops::Add<Output = Self::Cost> + std::marker::Copy;
type ChildrenIterator: Iterator<Item = (Self, Self::Cost)>;
fn cull(&self) -> Option<Self::Cost>;
fn children(&self) -> Self::ChildrenIterator;
}
Mais le compilateur se plaindra des définitions ambiguës en double. Par exemple dans l'implémentation DummyState
pour State
, il se plaindra que Self::Cost
est ambigu, car il ne peut pas dire si vous faites référence à <Self as State>::Cost
, ou <Self as RawState>::Cost
.
Êtes-vous sûr de vouloir deux traits, et pas seulement un trait avec une [implémentation par défaut] (https://doc.rust-lang.org/book/traits.html#default-methods) de 'children_with_cull'? – trentcl
@trentcl: Les types qui préfèrent implémenter 'RawState' directement devraient encore implémenter' children', même si 'children' n'est pas utilisé directement. –