В чем разница между универсальным типом черты и общим ассоциированным типом? - PullRequest
0 голосов
/ 20 февраля 2019

Этот вопрос задается до того, как родственные связанные типы доступны в Rust, хотя они предложены и разработаны .

Насколько я понимаю, универсальные признаки и связанные типыразличаются количеством типов, которые они могут связать со структурой.

Обобщения могут связывать любое количество типов:

struct Struct;

trait Generic<G> {
    fn generic(&self, generic: G);
}

impl<G> Generic<G> for Struct {
    fn generic(&self, _: G) {}
}

fn main() {
    Struct.generic(1);
    Struct.generic("a");
}

Связанные типы связать ровно 1 тип:

struct Struct;

trait Associated {
    type Associated;

    fn associated(&self, associated: Self::Associated);
}

impl Associated for Struct {
    type Associated = u32;

    fn associated(&self, _: Self::Associated) {}
}

fn main() {
    Struct.associated(1);
    // Struct.associated("a"); // `expected u32, found reference`
}

Общие связанные типы представляют собой смесь этих двух.Они связывают с типом ровно 1 связанный генератор, который, в свою очередь, может связывать любое количество типов.Тогда чем же отличается Generic от предыдущего примера от этого общего связанного типа?

struct Struct;

trait GenericAssociated {
    type GenericAssociated;

    fn associated(&self, associated: Self::GenericAssociated);
}

impl<G> GenericAssociated for Struct {
    type GenericAssociated = G;

    fn associated(&self, _: Self::GenericAssociated) {}
}

Ответы [ 2 ]

0 голосов
/ 21 февраля 2019

Давайте еще раз посмотрим на ваш последний пример (сокращенный мной):

trait GenericAssociated {
    type GenericAssociated;
}

impl<G> GenericAssociated for Struct {
    type GenericAssociated = G;
}

Это не имеет общие родственные типы!У вас просто есть универсальный тип в вашем блоке impl, который вы назначаете для связанного типа.Мм, хорошо, я вижу, откуда возникла путаница.

Ваш пример ошибок с «параметром типа G не ограничен признаком impl, self-типом или предикатами».Это не изменится, когда GAT будут внедрены, потому что, опять же, это не имеет ничего общего с GAT.

Использование GAT в вашем примере может выглядеть так:

trait Associated {
    type Associated<T>; // <-- note the `<T>`! The type itself is 
                        //     generic over another type!

    // Here we can use our GAT with different concrete types 
    fn user_choosen<X>(&self, v: X) -> Self::Associated<X>;
    fn fixed(&self, b: bool) -> Self::Associated<bool>;
}

impl Associated for Struct {
    // When assigning a type, we can use that generic parameter `T`. So in fact,
    // we are only assigning a type constructor.
    type Associated<T> = Option<T>;

    fn user_choosen<X>(&self, v: X) -> Self::Associated<X> {
        Some(x)
    }
    fn fixed(&self, b: bool) -> Self::Associated<bool> {
        Some(b)
    }
}

fn main() {
    Struct.user_choosen(1);    // results in `Option<i32>`
    Struct.user_choosen("a");  // results in `Option<&str>`
    Struct.fixed(true);        // results in `Option<bool>`
    Struct.fixed(1);           // error
}

Но, чтобы ответить на ваш главный вопрос:

В чем разница между чертойуниверсальный тип и универсальный связанный тип?

Вкратце: они позволяют задержать применение конкретного типа (или срока службы), что делает всю систему типов более мощной.

Существует множество мотивационных примеров в RFC , в частности, потоковый итератор и пример семейства указателей.Давайте быстро разберемся, почему потоковый итератор не может быть реализован с помощью обобщенных признаков.

GAT-версия потокового итератора выглядит следующим образом:

trait Iterator {
    type Item<'a>;
    fn next(&self) -> Option<Self::Item<'_>>;
}

В текущем Rust мы могли бы поместитьПараметр времени жизни на признаке вместо связанного с ним типа:

trait Iterator<'a> {
    type Item;
    fn next(&'a self) -> Option<Self::Item>;
}

Пока все хорошо: все итераторы могут реализовать эту черту, как и раньше.Но что, если мы хотим использовать это?

fn count<I: Iterator<'???>>(it: I) -> usize {
    let mut count = 0;
    while let Some(_) = it.next() {
        count += 1;
    }
    count
}

Какое время жизни мы должны аннотировать?Помимо аннотирования времени жизни 'static, у нас есть два варианта:

  • fn count<'a, I: Iterator<'a>>(it: I): это не сработает, потому что вызывающий объект выбирает универсальные типы функций.Но it (который станет self в вызове next) живет в нашем стековом фрейме.Это означает, что время жизни it не известно вызывающей стороне.Таким образом мы получаем компилятор ( Playground ).Это не вариант.
  • fn count<I: for<'a> Iterator<'a>>(it: I) (с использованием HRTB): похоже, это работает, но у него есть тонкие проблемы.Теперь нам требуется I для реализации Iterator для любого времени жизни 'a.Это не проблема для многих итераторов, но некоторые итераторы возвращают элементы, которые не существуют вечно, и поэтому они не могут реализовать Iterator для любого времени жизни - просто времена жизни, которые короче, чем их элемент.Использование этих границ с более высоким рейтингом часто приводит к секретным 'static границам, которые очень ограничивают.Так что это также не всегда работает.

Как вы можете видеть: мы не можем правильно записать границы I.И на самом деле, мы даже не хотим упоминать время жизни в сигнатуре count!Это не должно быть необходимо.И это именно то, что нам позволяют делать GAT (между прочим).С GAT мы могли бы написать:

fn count<I: Iterator>(it: I) { ... }

И это будет работать.Поскольку «применение конкретного времени жизни» происходит только тогда, когда мы вызываем next.

. Если вас интересует еще больше информации, вы можете взглянуть на мое сообщение в блоге «Решение обобщенной потоковой передачи».Проблема итератора без GAT », где я пытаюсь использовать универсальные типы для признаков, чтобы обойти отсутствие GAT.И (спойлер): обычно это не работает.

0 голосов
/ 20 февраля 2019

В чем разница?

Универсальные ассоциированные типы (GAT) - это связанные типы , которые сами по себе универсальные .RFC начинается с мотивационного примера , выделенного мной:

Рассмотрим следующую черту в качестве типичного мотивирующего примера:

trait StreamingIterator {
    type Item<'a>;
    fn next<'a>(&'a mut self) -> Option<Self::Item<'a>>;
}

Эта черта очень полезна- он допускает своего рода Итератор, который выдает значения, время жизни которых связано с временем жизни ссылки, переданной в next.Конкретным очевидным вариантом использования этой черты будет итератор по вектору, который дает перекрывающиеся изменчивые субликсы с каждой итерацией.При использовании стандартного интерфейса Iterator такая реализация была бы недействительной, поскольку каждый срез должен был бы существовать столько же, сколько итератор, чем столько, сколько заимствование, инициированное next.

Эта черта не может быть выражена в Rust, поскольку она существует сегодня , потому что она зависит от своего рода полиморфизма с более высоким родом.Этот RFC расширил бы Rust, чтобы включить эту специфическую форму полиморфизма с более высоким родом, который упоминается здесь как конструкторы связанного типа.Эта функция имеет ряд приложений, но основное приложение находится в том же ключе, что и черта StreamingIterator: определение черт, которые дают типы, срок жизни которых связан с локальным заимствованием типа получателя.

Обратите внимание, что связанный тип Item имеет общее время жизни 'a.Большинство примеров в RFC используют время жизни, но есть также пример, использующий универсальный тип :

trait PointerFamily {
    type Pointer<T>: Deref<Target = T>;
    fn new<T>(value: T) -> Self::Pointer<T>;
}

Обратите внимание, что связанный тип Pointer имеет универсальныйвведите T.

Ваш конкретный пример

В чем отличие Generic от предыдущего примера и этого общего связанного типа

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...