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

Я тоже борется с этим, решения set_error_handler не работали для меня, возможно, на основе различий в версии PHP.

Решение для меня заключалось в попытке обнаружить ошибку при выключении :

// Since set_error_handler doesn't catch Fatal errors, we do this
function shutdown()
{
    $lastError = error_get_last();
    if (!empty($lastError)) {
        $GLOBALS['logger']->debug(null, $lastError);
    }
}
register_shutdown_function('shutdown');

Я не уверен, почему деление на 0 выключается, а не обрабатывается set_error_handler, но это помогло мне выйти за пределы этого, просто молча умер.

7
задан Shepmaster 20 February 2019 в 17:32
поделиться

2 ответа

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

Родовые ассоциированные типы (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 не поможет вашему случаю, который, по-видимому, не требует ассоциированного типа, который сам по себе является общим.

0
ответ дан Shepmaster 20 February 2019 в 17:32
поделиться

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

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>;
}
[1141 ] Пока все хорошо: все итераторы могут реализовать эту черту, как и раньше. Но что, если мы хотим использовать это?

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» , где я пытаюсь использовать универсальные типы для признаков, чтобы обойти недостаток ГАТ. И (спойлер): обычно это не работает.

0
ответ дан Lukas Kalbertodt 20 February 2019 в 17:32
поделиться
Другие вопросы по тегам:

Похожие вопросы: