Любопытно повторяющийся шаблон общих черт: переполнение при оценке потребности - PullRequest
0 голосов
/ 02 июня 2018

Я пытаюсь реализовать общую структуру с кучей полей, где каждый из типов полей должен знать о точном типе всей структуры.Это своего рода шаблон стратегии.

pub struct Example<S: Strategy<Example<S, D>>, D> {
    pub s: S,
    pub a: S::Associated,
    pub data: D,
}
pub trait Strategy<T> {
    type Associated;
    fn run(&self, &T);
}
pub trait HasData {
    type Data;
    fn data(&self) -> &Self::Data;
}

impl<S: Strategy<Self>, D> Example<S, D> {
//               ^^^^
// the complex code in this impl is the actual meat of the library:
    pub fn do_it(&self) {
        self.s.run(self); // using the Strategy trait
    }
}
impl<S: Strategy<Self>, D> HasData for Example<S, D> {
    type Data = D;
    fn data(&self) -> &D {
        &self.data
    }
}

Я планировал создать экземпляры обобщений из вышеприведенной «библиотеки»:

pub struct ExampleStrat;
pub struct ExampleData;

impl<E: HasData<Data = ExampleData>> Strategy<E> for ExampleStrat {
    type Associated = ();
    fn run(&self, e: &E) {
        let _ = e.data();
        // uses ExampleData here
    }
}
let example = Example {
    s: ExampleStrat,
    a: (),
    data: ExampleData,
};
example.do_it();

В моем реальном коде я получил довольнонесколько разных «стратегий», а также несколько полей данных, так что тип Example имеет внушительный список обобщений, и я рад, если пользователь библиотеки не должен быть явным о них (или, по крайней мере, не часто) ивместо этого можно просто использовать черту HasData (со связанными типами, а не с параметрами универсального типа).

Если бы в struct Example<S, D> не было привязанного типа, это на самом деле работало бы (на удивление) хорошо, намного лучше, чемЯ изначально ожидал (после борьбы с Self в struct оценки ).Однако рекомендуется продублировать границы черт impl для struct, когда структура предполагается использовать только с ограниченными типами, и в моем случае мне нужно чтобы они могли использовать тип Associated для поля a.

Теперь компилятор жалуется

error[E0275]: overflow evaluating the requirement `main::ExampleStrat: Strategy<Example<main::ExampleStrat, main::ExampleData>>`
  --> src/main.rs:42:9
   |
42 |         a: (),
   |         ^^^^^
   |
   = note: required because of the requirements on the impl of `HasData` for `Example<main::ExampleStrat, main::ExampleData>`
   = note: required because of the requirements on the impl of `Strategy<Example<main::ExampleStrat, main::ExampleData>>` for `main::ExampleStrat`

Как я могу решить эту проблему? Я пытаюсь сделать что-то, что невозможно, я делаю это неправильно или это возможно, но я становлюсь жертвой ошибки компилятора ?Мой полный дизайн испорчен?

Ответы [ 2 ]

0 голосов
/ 02 июня 2018

Если для Strategy характерна следующая impl, то она может быть параметризована не на том месте.(Я собираюсь игнорировать связанный тип для этого ответа, потому что пример не использует его.)

impl<E: HasData<Data = ExampleData>> Strategy<E> for ExampleStrat {
    fn run(&self, e: &E) {
        let _ = e.data();
        // uses ExampleData here
    }
}

Вместо этого вы можете параметризовать Strategy сверх D - ломая impl цикл зависимости - и параметризация только метода run над E.

pub trait Strategy<D> {
    fn run(&self, &impl HasData<Data = D>);
}

impl Strategy<ExampleData> for ExampleStrat {
    fn run(&self, e: &impl HasData<Data = ExampleData>) {
        let _ = e.data();
        // uses ExampleData here
    }
}

fn run<E: HasData<Data = ExampleData>>(&self, e: &E) - это еще один способ определить run, который является одинаковым для этой цели. Вот полный пример .

Потенциальным недостатком этого подхода является то, что run нельзя вызывать через объект признака Strategy, поскольку он должен быть мономорфизирован для любоготип, который реализует HasData.Но черта HasData, похоже, мало что делает в этом impl: единственное, что она может сделать, это вернуть внутреннюю ссылку, и как только она у вас есть, нет смысла использовать ее снова.Может быть, run может просто взять ссылку &D?

pub trait Strategy<D> {
    fn run(&self, &D);
}

impl Strategy<ExampleData> for ExampleStrat {
    fn run(&self, _: &ExampleData) {
        // uses ExampleData here
    }
}

Конечно, теперь вам нужно позвонить self.s.run(self.data()) в do_it, но это не будет стоить вам гибкости по сравнению соригинальная версия, в которой, если бы она работала…, вы могли бы вызывать Strategy<E>::run только с аргументом типа &E.

На самом деле, вся черта HasData кажется мне ненужной: она всегда реализуетсятот же тип, реализация которого вызывает его, поэтому, за исключением незначительного удобства передачи self вместо self.data, он не повышает уровень абстракции внутри метода do_it.Таким образом, мне кажется, что одно и то же удалить HasData полностью и дать Example знать, как вызвать Strategy::run с правильной ссылкой;в любом случае(Однако, возможно, мне просто не хватает воображения.)

Любое из этих решений должно обрабатывать добавление связанного типа к Strategy, но, не зная, как он будет использоваться, трудно сказать наверняка.

tIt можно заставить работать в какой-то будущей версии компилятора с достаточно умной проверкой типов.

0 голосов
/ 02 июня 2018

Прежде всего, все становится намного понятнее, если вы избегаете устанавливать границы черт для определений структур и черт.Когда все усложняется, ограничения, по крайней мере, решаются в одном направлении.

pub struct Example<S, D, A> {
    pub s: S,
    pub a: A,
    pub data: D,
}

pub trait Strategy<T> {
    type Associated;
    fn run(&self, &T);
}

pub trait HasData {
    type Data;
    fn data(&self) -> &Self::Data;
}

impl<S, D, A> Example<S, D, A>
where
    S: Strategy<Self, Associated = A>,
{
    pub fn do_it(&self) {
        self.s.run(self);
    }
}

impl<S, D, A> HasData for Example<S, D, A>
where
    S: Strategy<Self, Associated = A>,
{
    type Data = D;
    fn data(&self) -> &D {
        &self.data
    }
}

Ваша реализация Strategy для ExampleStrat выглядит следующим образом:

impl<E: HasData<Data = ExampleData>> Strategy<E> for ExampleStrat {
    type Associated = ();
     // ...
}

Что этоозначает, что вы определяете его для всех возможных квалификационных типов E.Средство проверки типов теперь может просматривать только границы признаков, которые снова являются общими и выражаются только в терминах других признаков, которые используют друг друга в качестве границ, поэтому средство проверки типов попадает в цикл.Поместите блок в цикл, задав ему конкретный тип, который вы знаете.

pub struct ExampleStrat;
pub struct ExampleData;

impl Strategy<Example<ExampleStrat, ExampleData, ()>> for ExampleStrat {
    type Associated = ();
    fn run(&self, e: &Example<ExampleStrat, ExampleData, ()>) {
        let _ = e.data();
        // uses ExampleData here
    }
}

fn main() {
    let example = Example {
        s: ExampleStrat,
        a: (),
        data: ExampleData,
    };
    example.do_it();
}
...