
Size: a a a
trait Provide<Item> {
fn provide(&mut self) -> Item;
}
И строительные блоки:struct HNil;
struct HCons<H, T>(H, T);
Первое, что приходит на ум — это реализация в лоб, т. е. реализовать трейт для HCons
в том случае, если его реализует либо голова, либо хвост:impl<Item, H, T> Provide<Item> for HCons<H, T>
where
H: Provide<Item>,
{
fn provide(&mut self) -> Item {
self.0.provide()
}
}
impl<Item, H, T> Provide<Item> for HCons<H, T>
where
T: Provide<Item>,
{
fn provide(&mut self) -> Item {
self.1.provide()
}
}
Даже без запуска понятно, что это не сработает: если и голова, и хвост реализуют Provide<Item>
, то у нас выходит две различные реализации для HCons
. И действительно, компилятор жалуется:error[E0119]: conflicting implementations of trait
Provide<_>
for type HCons<_, _>
Provide
из головы и Provide
из хвоста и потом объединить их при помощи blanket impl, но мы опять упрёмся в перекрывающиеся реализации. Так что же делать?provide
, а потому формально это будут два разных трейта (точнее, один, параметризованный двумя разными наборами тИповых аргументов). Явно этот путь нам прописывать не нужно, за нас его напишет вывод типов.Provide
и внести в него ещё один тИповой параметр:trait Provide<Item, Path> {
fn provide(&mut self) -> Item;
}
И, конечно, добавить типовые маркеры, отвечающие за компоненты пути:struct Itself;
struct Head<T>(T);
struct Tail<T>(T);
Теперь модифицируем реализации для Hcons
:impl<Item, H, T, HSource> Provide<Item, Head<HSource>> for HCons<H, T>
where
H: Provide<Item, HSource>,
{
fn provide(&mut self) -> Item {
self.0.provide()
}
}
impl<Item, H, T, TSource> Provide<Item, Tail<TSource>> for HCons<H, T>
where
T: Provide<Item, TSource>,
{
fn provide(&mut self) -> Item {
self.1.provide()
}
}
И... Оно компилируется. Что ж, сделаем пример:struct Cloning<T>(T);
impl<T> Provide<T, Itself> for Cloning<T>
where
T: Clone,
{
fn provide(&mut self) -> T {
self.0.clone()
}
}
fn _test() {
let mut list = HCons(Cloning(0), HCons(Cloning(()), HNil));
let _: u32 = list.provide();
let _: () = list.provide();
}
Как видите, всё прекрасно работает, нужные типы выводятся по возвращаемому результату. Сила вывода типа!let mut list = HCons(Cloning(()), HCons(Cloning(()), HNil));
let _: u32 = list.provide();
Ответ компилятора:error[E0277]: the trait bound `HCons<Cloning<()>, HCons<Cloning<()>, HNil>>: Provide<u32, _>` is not satisfied
--> src/lib.rs:47:23
|
47 | let _: u32 = list.provide();
| ^^^^^^^ the trait `Provide<u32, _>` is not implemented for `HCons<Cloning<()>, HCons<Cloning<()>, HNil>>`
Что ж, вполне логичный ответ. А теперь для конфликтующих провайдеров:let mut list = HCons(Cloning(()), HCons(Cloning(()), HNil));
let _: () = list.provide();
Ответ компилятора:error[E0282]: type annotations needed
--> src/lib.rs:47:23
|
47 | let _: () = list.provide();
| ^^^^^^^ cannot infer type for type parameter `Path` declared on the trait `Provide`
Что ж, ошибка явно могла бы быть более внятной, но, по крайней мере, она есть.Provide<Item, Itself>
для своих типов, ибо навряд ли они такие же обобщённые. Несколько бойлерплейтно. Обходится созданием отдельного трейта, который будет реализовывать Provide<_, Itself>
через blanket impl:trait ProvideBase<Item> {Во-вторых, при реализации
fn provide_base(&mut self) -> Item;
}
impl<Item, T> Provide<Item, Itself> for T
where
T: ProvideBase<Item>,
{
fn provide(&mut self) -> Item {
self.provide_base()
}
}
Provide
можно использовать произвольные типы для путей, что позволяет немного сломать код:impl Provide<u32, Head<Itself>> for HCons<(), HNil> {
fn provide(&mut self) -> u32 {
0
}
}
// где-то в коде
let _: u32 = HCons(Cloning(42u32), HCons((), HNil)).provide();
// ^~~ error: type annotations needed
Это было бы неприятным, если бы мы строили вокруг этого библиотеку, даром, что ломается пользовательский код. Оградиться от этого, теме не менее, достаточно просто: сделаем sealed trait для типов-компонент путей, повесим в качестве ограничения на тип Path
в определении Provide
, и реализуем для Head
, Tale
и Itself
, но Itself
оставим единственным публичным типом.trait ProvideExt<T> {
fn provide_via_path<P>(&mut self) -> T
where
Self: Provide<T, P>;
}
impl<Item, T> ProvideExt<Item> for T {
fn provide_via_path<P>(&mut self) -> Item
where
Self: Provide<Item, P>,
{
self.provide()
}
}
Ну и прежде чем закончить, хочу отметить, что решение с путями на уровне типов я придумал не на пустом месте, а вдохновившись вот этим постом Дэдфуда. Вот и всё, как всегда, весть код в гисте.