Rust: 🔬 Проблема с отслеживанием общих связанных типов (GAT)

Созданный на 2 сент. 2017  ·  67Комментарии  ·  Источник: rust-lang/rust

Самый полезный комментарий

https://github.com/rust-lang/rust/issues/44265#issuecomment -568247656 — это (несколько краткое) обновление.

67510 — это последняя важная функция ICE/отсутствующая, которую необходимо реализовать.

Все 67 Комментарий

Вот своего рода план реализации, который я буду стараться обновлять.

  • [ ] Шаг первый: добавьте поддержку в AST и красиво распечатайте

    • Вероятно, первым шагом будет запуск парсинга новых форм и добавление их поддержки в AST.

    • Смотрите этот комментарий для более подробных мыслей здесь .

    • Мы должны быть в состоянии написать несколько тестов, предназначенных только для синтаксического анализа, а также протестировать работу красивого принтера.

    • Когда мы добираемся до снижения HIR , мы можем ошибиться, если какой-либо GAT присутствует.

    • Мы также можем сделать функциональные ворота, тогда

  • [ ] Еще не все

Позвольте мне начать с более подробного описания AST. Сначала давайте обсудим, как это работает сегодня :

Элемент type Foo: Bar [= Baz]; в определении признака определяется этим вариантом AST . Это включает в себя границы ( Bar ) и (необязательное) значение по умолчанию Baz . Имя определяется в структуре TraitItem .

Элемент type Foo = Bar; в реализации признака определяется этим вариантом AST , который включает только тип Bar , потому что Foo и т. д. определен в ImplItem структура .

Методы представляют собой интересный случай, поскольку их уже можно сделать универсальными. Эти общие параметры объявляются в поле Generics структуры MethodSig . Это экземпляр структуры Generics .

Я считаю, что лучше всего было бы «поднять» Generics из методов в TraitItemImplItem ), чтобы это в равной степени применялось ко всем формам трейты и импл элементы. На данный момент мы не будем поддерживать общие константы, я думаю, но, честно говоря, они, вероятно, просто выпадают из работы, которую мы делаем в любом случае, поэтому они будут небольшим расширением. Я думаю, что работа пойдет лучше, если мы просто запланируем их сейчас.

Возможно, достойным первым пиаром было бы просто внести это изменение , сохранив все остальные существующие функции прежними. То есть мы увеличили бы Generics до TraitItemImplItem ) и из MethodSig . Мы бы поставили пустой Generics для не-методов. Мы прорабатывали существующий код, добавляя дженерики по мере необходимости, чтобы он работал.

@nikomatsakis Круто! Большое спасибо! Я начал экспериментировать с этим прошлой ночью и с гордостью могу сказать, что нашел те же самые места, на которые вы указали в своем комментарии о AST. :smile: (Учитывая, что это был мой первый раз в rustc, я считаю это достижением!)

Я не думал поднимать дженерики в TraitItem . Мой подход заключался в том, чтобы поместить Generics в TraitItemKind::Type , поскольку там уже хранится объявление типа. Ваш подход тоже имеет смысл, поэтому я буду работать над его реализацией. Поскольку я все еще совершенно новичок в этой кодовой базе, мне интересно узнать, какие подводные камни были бы у моего подхода, если бы он использовался вместо того, который вы предложили. Не могли бы вы дать мне некоторое представление о вашем мыслительном процессе? :смайлик:

Вот изменение, которое я бы сделал:

pub enum TraitItemKind {
    // Generics aren't supported here yet
    Const(P<Ty>, Option<P<Expr>>),
    // `Generics` is already a field in `MethodSig`
    Method(MethodSig, Option<P<Block>>),
    // Added `Generics` here:
    Type(Generics, TyParamBounds, Option<P<Ty>>),
    Macro(Mac),
}

Изменить: ответ nikomatakis на Gitter

о подводных камнях ввода их в тип
Я думаю, это тоже может сработать
причина, по которой я не хотел этого делать
заключается в том, что мы действительно хотим делать одни и те же вещи (по крайней мере теоретически) для методов и типов
и, как я уже сказал, в принципе я не вижу причин, по которым мы не могли бы сделать то же самое для констант
Я думаю, если вы просто переместите дженерики в вариант типа
это, вероятно, будет работать нормально, но если вы посмотрите, прямо сейчас нам часто приходится делать «одно для типов/констант, одно для методов» именно потому, что они разные
поэтому я подозреваю, что код станет более однородным
Я не совсем уверен, как это будет, если честно =) -- это может быть больно
но во многих случаях делать вещи более общими, чем они должны быть, не так уж и плохо, потому что вы можете вставить span_bug! звонит в невозможных пока случаях (а позже мы приедем и подлатаем их)

Отлично! Следующим шагом является расширение парсера. Вот несколько советов. Начнем с элементов черт.

Эта подпрограмма анализирует элементы свойств . Мы хотим расширить случай, который обрабатывает связанные типы , чтобы также анализировать такие вещи, как type Foo<....> = ...; (также, возможно, предложения where). ( <...> — это «дженерики», которые мы только что добавили в AST.)

В настоящее время он использует parse_ty_param , который в основном анализирует что-то вроде T: Foo и т. д. Нам придется прекратить это делать, потому что грамматика для объявлений связанных типов больше не соответствует грамматике для параметров типа. Поэтому мы, вероятно, захотим добавить что-то вроде parse_trait_item_assoc_ty . Это может начаться как своего рода клон parse_ty_param() , но затем мы захотим изменить его, чтобы вызвать parse_generics() прямо здесь. Эта подпрограмма будет анализировать объявление дженериков ( <...> ), если оно присутствует, в противном случае она просто возвращает пустые дженерики. Затем мы хотим добавить вызов для синтаксического анализа предложений where прямо здесь — вы можете смоделировать это на вызове, который происходит при синтаксическом анализе методов , обратите внимание, что результат сохраняется в generics , который мы проанализировали ранее.

Как только мы это сделаем, мы сможем добавить несколько тестов для синтаксического анализа. Я бы сделал это, создав каталог вроде src/test/run-pass/rfc1598-generic-associated-types/ и добавив туда файлы, которые вы ожидаете успешно проанализировать. Прямо сейчас они не будут работать правильно, но это не имеет значения. Просто добавьте пустую основную функцию. Затем мы также можем добавить примеры, которые не должны анализироваться в src/test/ui/rfc1598-generic-associated-types/ (см. инструкции по добавлению тестов пользовательского интерфейса в COMPILER_TESTS.md ).

Кое-что еще — нам нужно показать эту работу на этом этапе, чтобы люди не использовали этот материал в стабильных сборках. Здесь, на Forge, есть несколько инструкций по добавлению ворот функций (см. последний раздел). Мы должны добавить visit_trait_item и visit_impl_item к посетителю в feature_gate.rs ; если этот элемент не является методом, но имеет непустой дженерик, мы можем вызвать gate_feature_post ( пример ).

Чтобы настроить разрешение имен, я думаю , все, что нам нужно сделать, это установить правильные «ребра» (материал разрешения имен организует наборы имен, которые находятся в области действия, в ребра; каждое ребро представляет один уровень привязки). например, для реализации:

impl<A,B> Foo<B> for Vec<A> {
   fn bar<T,U>(x: ...) { 
       for y in ... {
       }
   }
}

у нас будут следующие ребра:

- <A,B> (from the impl)
   - <T,U> (from the `bar` method's generics)
      - `x` (from the parameter list)
          - `y` (from the let)

В общем, моделирование того, как работают методы, — неплохая идея. Я полагаю, мы могли бы также сделать здесь небольшую «проверку на будущее».

Вот код, который переносит параметры типа метода в область видимости (это для метода, определенного в трейте):

https://github.com/rust-lang/rust/blob/a35a3abcda67a729edbb7d649dbc663c6feabd4c/src/librustc_resolve/lib.rs#L1890 -L1892

В то время как для type , определенного в свойстве, мы жестко запрограммированы на добавление пустого параметра типа rib ( NoTypeParameters ):

https://github.com/rust-lang/rust/blob/a35a3abcda67a729edbb7d649dbc663c6feabd4c/src/librustc_resolve/lib.rs#L1897 -L1901

Теперь, когда дженерики присутствуют в каждом элементе типаж/имплементации, я думаю, мы, вероятно, захотим удалить обработку для type и извлечь обработку метода, чтобы она происходила на более высоком уровне. Для элементов (например, const ), где нет дженериков, вновь введенное ребро должно быть пустым и, следовательно, безвредным (надеюсь).

Другие интересные места:

Вы поняли идею.

@petrochenkov -- звучит примерно так?

@никомацакис

звучит примерно так?

Все выглядит правильно.

Следующий шаг. Пожизненное разрешение.

К лучшему или к худшему, в настоящее время это делается в совершенно отдельном фрагменте кода от другого разрешения имен. Это потому, что это происходит после создания HIR. Почти наверняка это изменится, но пока не изменилось.

Основные идеи такие же, как и в обычном разрешении имен, за исключением того, что мы называем вещи не «ребрами», а «областями». знак равно

Есть некоторые легкие осложнения из-за этой концепции «поздних» жизней. Однако это не очень важно здесь - все времена жизни для универсального ассоциированного типа будут "ранними связями", что является довольно простым случаем. Время жизни с «поздней привязкой» — это время жизни, объявленное для метода или функции, значение которой не предоставляется до тех пор, пока метод не будет вызван. Я не буду вдаваться в подробности здесь, потому что это не так важно — главное, что мы не хотим следовать точно той же модели для методов, что и для других видов универсальных элементов, в отличие от другого разрешения имен. случаи.

Вот пример кода. Это код, который посещает impl , struct или другой нефункциональный элемент. В этих случаях, как и в GAT, мы в основном хотим перенести все параметры времени жизни из Generics в область действия и сопоставить их с временем жизни с «ранней привязкой»:

https://github.com/rust-lang/rust/blob/ddaebe938b8eb1f5e17570ae8091743972e02bdd/src/librustc/middle/resolve_lifetime.rs#L370 -L388

Вы можете видеть, что он сначала создает вектор жизней, вызывая Region::early для каждого из них:

https://github.com/rust-lang/rust/blob/ddaebe938b8eb1f5e17570ae8091743972e02bdd/src/librustc/middle/resolve_lifetime.rs#L376 -L378

Затем он создает Scope . Переменная next_early_index просто подсчитывает, сколько жизней с ранней привязкой находится в области видимости. Поскольку этот код специфичен для элементов, это всегда будет число ранних жизней, объявленных для этого текущего элемента. (Позже мы рассмотрим случай, когда мы вводим в область действия дополнительные времена жизни, что больше всего нам нужно для GAT.)

https://github.com/rust-lang/rust/blob/ddaebe938b8eb1f5e17570ae8091743972e02bdd/src/librustc/middle/resolve_lifetime.rs#L379 -L384

Наконец, мы вызываем with() . Этот метод перенесет область в область видимости и вызовет закрытие. Любые воплощения, которые мы посещаем внутри этого закрытия, будут видеть те имена, которые мы только что определили как находящиеся в области видимости:

https://github.com/rust-lang/rust/blob/ddaebe938b8eb1f5e17570ae8091743972e02bdd/src/librustc/middle/resolve_lifetime.rs#L385 -L388

Хорошо, теперь давайте рассмотрим еще один пример. Этот случай охватывает "черты реализации". Детали того, что он делает, не так уж важны (то есть вам не нужно выполнять дешугаринг на impl Trait как таковой). Достаточно сказать, что это вводит в область видимости некоторые новые ранние сроки жизни — это именно то, что мы собираемся сделать для GAT.

https://github.com/rust-lang/rust/blob/ddaebe938b8eb1f5e17570ae8091743972e02bdd/src/librustc/middle/resolve_lifetime.rs#L482 -L501

Позвольте мне выделить несколько вещей. Во-первых, метод next_early_index возвращает следующий неназначенный ранний индекс:

https://github.com/rust-lang/rust/blob/ddaebe938b8eb1f5e17570ae8091743972e02bdd/src/librustc/middle/resolve_lifetime.rs#L488

Это обеспечивает отправную точку. Затем мы снова используем Region::early для создания новых определений времени жизни с ранней привязкой, которые будут разрешены:

https://github.com/rust-lang/rust/blob/ddaebe938b8eb1f5e17570ae8091743972e02bdd/src/librustc/middle/resolve_lifetime.rs#L490 -L492

Наконец, мы вносим их в область видимости, снова вызывая with :

https://github.com/rust-lang/rust/blob/ddaebe938b8eb1f5e17570ae8091743972e02bdd/src/librustc/middle/resolve_lifetime.rs#L494 -L501

Хорошо, это два примера. Мы собираемся сделать что-то очень похожее на второй. Мы хотим изменить определения этих двух методов:

https://github.com/rust-lang/rust/blob/ddaebe938b8eb1f5e17570ae8091743972e02bdd/src/librustc/middle/resolve_lifetime.rs#L509

https://github.com/rust-lang/rust/blob/ddaebe938b8eb1f5e17570ae8091743972e02bdd/src/librustc/middle/resolve_lifetime.rs#L520

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

Так что я вскочил на это ранее сегодня. Я хотел бы убедиться, что я на правильном пути:

  • Все, что нужно было сделать, это добавить время жизни на карту, а затем пройтись по элементу черты/импли? Проверка, кажется, работает, хотя трудно сказать (см. далее)... может работать только в простом (неограниченном) случае.
  • Я снял запреты на параметры типа для qpath_to_ty и associated_path_def_to_ty в librustc_typeck/astconv.rs , чтобы исправить ошибки type parameters are not allowed on this type . Я думаю, что это нужно заменить некоторыми проверками. Также...
  • Я получаю сбои от typeck сейчас. (обратная связь, в частности)

сбои typeck вызываются src/test/compile-fail/struct-path-associated-type.rs , потому что он предоставляет универсальные значения для значений, которые не имеют связанного типа.

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

Собираюсь работать над этим, но ценятся указания на то, иду ли я вообще в правильном направлении.

Привет, @brandonson! Я ненавижу отговаривать кого-либо от взлома компилятора Rust, но я думаю, что @sunjay уже активно занимался тем же самым материалом и как бы добивался этого изменения с самого начала, поэтому, вероятно, для них имеет смысл закончить. это изменение тоже (думаю, они уже начались). Я не уверен, есть ли очевидный способ распараллелить эти усилия (конечно, это возможно, но нам придется немного продумать шаги заранее).

Однако, если вы хотите найти что-то для решения, могу ли я порекомендовать исправить некоторые ошибки на этом этапе? https://github.com/rust-lang/rust/issues/46472 , кажется, не используется, я могу попытаться оставить некоторые комментарии там в ближайшее время.

Конечно, я не хочу наступать кому-либо на пятки, но я не видел ничего, указывающего на то, что дальнейший прогресс действительно происходит (хотя, по общему признанию, комментарий относительно следующих шагов был сделан довольно недавно). На самом деле, я начал пытаться решить эту проблему, потому что за последние пару дней несколько раз хотел получить GAT, поэтому, хотя я не обязательно буду возражать против работы над некоторыми вещами NLL в будущем, это намного выше в моем списке вещей, которые нужно решить. В данный момент.

@sunjay , если вы все еще активно работаете / планируете работать над этим, пожалуйста, дайте мне знать - нет смысла заставлять меня дублировать вашу работу над этим.

Привет, @brandonson , спасибо за твое рвение и готовность помочь. :) Я действительно активно работаю над этим. Я шаг за шагом прорабатывал каждую часть реализации, тесно сотрудничая с Нико, чтобы сделать все правильно.

Я сделаю все, что в моих силах, чтобы выпустить это как можно скорее. Я тоже очень хочу эту функцию!

Как продвигается прогресс? =) Не терпится поэкспериментировать с этим в nightly <3

Интересно, как будет работать перекрестное сечение GAT и const generics и было ли это частью предложений при объединении?

Пример того, что я имею в виду:

trait Foo {
    type Bar<const N>;
}

Привет , @sunjay. Я думаю, что это довольно важная функция, она широко упоминалась в комментариях к дорожной карте 2018 года. Как это продвигается? Спасибо за вашу работу!

Это моя самая желанная функция на данный момент. Я надеюсь, что это станет приоритетом и скоро появится в ночных выпусках!

Итак, недавно я встретился с @sunjay , который сейчас занят в школе и другими делами, чтобы попытаться изложить здесь следующие шаги. В какой-то момент мы с ними встретились и обсудили общую стратегию реализации, кульминацией которой стал коммит в их репозитории, где мы оставили кучу встроенных комментариев .

Я думаю, что стратегия, которая имеет наибольший смысл в будущем, состоит из двух частей:

  • Во-первых, нам нужно написать еще несколько тестов.
  • В нашем парсере и некоторых других «внешних» частях системы есть некоторые известные недостатки, нам нужно их перечислить и исправить. Мы, вероятно, найдем больше в тестах.
  • На этом этапе мы готовы начать взламывать систему черт:

    • некоторая основа уже заложена, так что, надеюсь, это в значительной степени задача «рефакторинга».

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

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

это прекрасно !! удачи @sunjay в этом и во всех других начинаниях.

Первым шагом будет убедиться, что у нас есть полный набор тестов. Существующие тесты можно найти в:

src/test/ui/rfc1598-generic-associated-types

Глядя на них, мы уже можем видеть, что часть работы предстоит сделать:

  • [ ] construct_with_other_type.rs -- выдает неожиданную ошибку E0110
  • [x] empty_generics -- проверяет, что type Bar<,> выдает ошибку, все в порядке
  • [x] generic-associated-types-where.rs -- проверяет, можем ли мы разобрать предложения where в нужных местах, кажется, все в порядке
  • [ ] generic_associated_type_undeclared_lifetimes.rs -- выдает неожиданную ошибку E0110
  • [ ] iterable.rs -- выдает неожиданную ошибку E0110
  • [ ] pointer_family.rs -- выдает неожиданную ошибку E0109
  • [ ] streaming_iterator.rs -- выдает неожиданную ошибку E0110

Места, где нет покрытия

  • [ ] в настоящее время у нас не так много тестов «ожидаемого использования», т. е. вещей, которые должны пройти успешно

    • pointer_family кажется в этом направлении

    • Я бы ожидал какого-нибудь теста trait Iterable { type Item; type Iter<'a>: Iterator<Item = &'a Self::Item>; }

  • [ ] пожизненные тесты теневого копирования — обычно мы запрещаем теневое копирование в течение всего срока службы, поэтому они должны быть незаконными:

    • trait Foo<'a> { type Item<'a>; }

    • impl<'a> Foo<'a> for &'a u32 { type Item<'a> = i32; }

  • [ ] «полный» синтаксис, похоже, не тестировался

    • например, тест pointer_family имеет Self::Pointer<T> , но не <Self as PointerFamily>::Pointer<T>

  • [ ] Неверное количество аргументов GAT. например, учитывая приведенное выше определение Iterable :

    • <T as Iterable>::Item -- вообще без параметров? Плохой.

    • Обратите внимание, что мы могли бы согласиться с этим в некоторых контекстах, поскольку в некоторых сопоставимых контекстах мы допускаем пропуск времени жизни.

      Я мог бы пойти в любую сторону здесь; Я бы предпочел, чтобы в подобных случаях люди писали явно '_ .

    • <T as Iterable>::Item<'_> -- Правильно!

    • <T as Iterable>::Item<T> -- Слишком много типов!

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

  • [ ] Значения по умолчанию для связанных типов? trait Foo { type Bar<T, U = T> where T: PartialEq<U>; }

    • в этом случае SomeType::Bar<u32> будет меньше SomeType::Bar<u32,u32> , что мы должны проверить.

Исправление неожиданных ошибок E0110

Об ошибке E0110 сообщает prohibit_type_params :

https://github.com/rust-lang/rust/blob/e65547d4fad0425d1db4f33a4d8134bf2cad939e/src/librustc_typeck/astconv.rs#L912

Первый шаг — выяснить, откуда это вызывается. Мой предпочтительный способ сделать это — получить локальную сборку и использовать -Ztreat-err-as-bug сочетании с RUST_BACKTRACE=1 . Но я не могу показать вам эти результаты, потому что rustc все еще находится в процессе сборки. :P Так что вместо этого я сделал быстрый rg prohibit_type_params , позвольте мне быстро указать на один подозрительный случай, который я вижу.

Один вызов из associated_path_def_to_ty :

https://github.com/rust-lang/rust/blob/e65547d4fad0425d1db4f33a4d8134bf2cad939e/src/librustc_typeck/astconv.rs#L791 -L803

Как указано в комментарии, это вызывается для разрешения компонента Pointer<T> в пути, подобном Self::Pointer<T> (обратите внимание, что параметры типа считаются частью сегмента пути вместе с именем, к которому они присоединены ). До GAT параметры типа были недопустимы (например, T::Item ), поэтому у нас просто есть общее ограничение:

https://github.com/rust-lang/rust/blob/e65547d4fad0425d1db4f33a4d8134bf2cad939e/src/librustc_typeck/astconv.rs#L810

Ясно, что этого не будет. Мы должны удалить эту строку и заменить ее какой-либо проверкой того, что, если параметры указаны, они соответствуют ожидаемому числу. Для этого нам, вероятно, нужен код проверки ошибок, подобный тому, что находится в create_substs_for_ast_path . Мы, вероятно, хотим создать здесь некоторый общий код, особенно для учета по умолчанию — может быть, мы действительно можем повторно использовать эту функцию?

Кто-нибудь еще работает над этим? Мне кажется, что до этого еще далеко. GAT — мой самый желанный RFC. Если нет, я хотел бы внести некоторые тесты...

@rickyhan , так что @Centril и @gavento говорили о WG-traits о разделении тестовой работы, но я не знаю, был ли достигнут какой-либо прогресс. Может быть, они смогут вмешаться. Я думаю, что PR будет приветствоваться. знак равно

Извините, если я глуп, но будут ли такие вещи законными с GAT?

trait Sequencer {
    type Wrap<A>;
    fn chain<A, B, F>(Self::Wrap<A>, F) -> Self::Wrap<B>
        where F: FnOnce(A) -> Self::Wrap<B>;
    fn wrap<A>(A) -> Self::Wrap<A>;
}

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

@mark-im Я попробовал на прошлой неделе. Насколько я понимаю, парсер синтаксиса есть (хотя и отсутствуют тесты). Но "реализация" еще не написана. (подробнее см. https://github.com/rust-lang/rust/issues/44265#issuecomment-330915766)

@quadrupleslap AIUI, это будет возможно позже, но сначала GAT будут поддерживать только параметры времени жизни..

@Boscop RFC указывает, что параметры типа также будут поддерживаться.

Кто-нибудь знает точный статус реализации синтаксиса в rustc? Кажется, в основном там, но более высокие оценки генерируют ICE:
http://play.rust-lang.org/?gist=a48959858ed5dd432c2396feae5c3cc1&version=nightly&mode=debug

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

Мне кажется, что коммуникация является основной проблемой, замедляющей внедрение GAT. Кажется, есть по крайней мере несколько человек, заинтересованных в работе над этим, но никто на самом деле не знает точного статуса реализации и того, кто уже работает над этим. Что вы думаете о канале Discord (или IRC?) просто для обсуждения внедрения GAT? Я думаю, это, безусловно, помогло бы.

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

Поэтому я попросил отдельный канал в Discord. Но вместо того, чтобы получить канал, я узнал несколько вещей. Я также искал все, что связано с GAT. Итак, я попытаюсь обобщить всю информацию об этой функции; возможно, это полезно для некоторых людей. Но я ничего не знаю, так что отнеситесь к этому с недоверием! Также: пожалуйста, сообщите мне, если какая-то информация неверна, чтобы я мог это исправить.

Резюме усилий по внедрению в отношении GAT

С тех пор никаких тестов пользовательского интерфейса не добавлялось . И я не могу найти никаких других PR, непосредственно связанных с GAT.

Однако пункт (с) сверху (система черт) прорабатывается «тайно». Насколько я понимаю, план состоит в том, чтобы в ближайшее время перейти на новый решатель признаков на основе мела и не заставлять GAT работать на старой системе. Интеграция нового решателя признаков отслеживается с помощью проблемы отслеживания «Меление» . Было довольно много PR, связанных с мелом и мелением. Примечательно, что существует меловой PR под названием «Завершить внедрение GAT» (объединено 24 мая 2018 г.). Таким образом, похоже, что базовая система для GAT уже существует.

Тем не менее, «GAT» в меле — это реализация прототипа, и его использование в rustc — это не просто use chalk; . Как сказал мне @scalexm : «Кажется, [что нужно сделать] довольно много».


Для получения дополнительной информации и помощи, вероятно, полезно заглянуть в канал Discord #wg-traits и узнать о проблеме с отслеживанием трейтов WG .

Итак , @nikomatsakis только что создал канал #wg-traits-gat на сервере разногласий rust-lang ( присоединяйтесь сюда ). Было бы здорово, если бы мы могли собрать всех, кто хочет помочь. Плюс несколько человек, которые знают о состоянии этой фичи (в частности, что еще нужно сделать и где мы могли бы помочь). Это должно сделать общение проще и быстрее, особенно для таких людей, как я, которые еще не глубоко вовлечены/не являются частью черт WG :)

Есть ли какие-либо обновления по этому поводу в последнее время? Ждем интеграции Chalk в компилятор? (Возможно, для этого есть отдельная тема.)

Возможно, для этого есть отдельная тема.

48049

Как примечание (возможно, известное), этот код вызывает панику у компилятора:

use typenum::{U1,U2,U3,Unsigned};

trait Array {
    type Of<Elem> ;
}

impl Array for U1 { type Of<T> = [T;1]; }
impl Array for U2 { type Of<T> = [T;2]; }
impl Array for U3 { type Of<T> = [T;3]; }

@wdanilo : кажется, это https://github.com/rust-lang/rust/issues/64755.

@varkor круто , спасибо!

Еще одно примечание (опять же, возможно, известное). Имея GAT, мы могли бы хорошо абстрагироваться от типов & и &mut , чтобы перестать постоянно копировать и вставлять код между функциями my_func и my_func_mut :) Вот возможная реализация такой абстракции (псевдо HKT):

#![feature(arbitrary_self_types)]
#![feature(generic_associated_types)]

use std::marker::PhantomData;

struct Pure <'t> (PhantomData<&'t()>);
struct Mut  <'t> (PhantomData<&'t()>);

type Ref<M, T> = <M as Acc>::Pat<T>;

trait Acc { type Pat<T: ?Sized>; }
impl<'t> Acc for Pure <'t> { type Pat<T> = PureRef <'t, T>; }
impl<'t> Acc for Mut  <'t> { type Pat<T> = MutRef  <'t, T>; }

struct PureRef <'t, T: ?Sized> (&'t     T);
struct MutRef  <'t, T: ?Sized> (&'t mut T);


/// USAGE ///

struct Buf<T> {
    data: Vec<T>
}

impl<T> Buf<T> {
    fn as_mut<M: Acc>(s: Ref<M, Self>) -> Ref<M, [f32]>
    {
        unimplemented!()
    }
}

Он почти компилируется. Мы также можем реализовать его без GAT, но типы взрываются:

#![feature(arbitrary_self_types)]

use std::marker::PhantomData;
use std::ops::Deref;

struct Pure <'t> (PhantomData<&'t()>);
struct Mut  <'t> (PhantomData<&'t()>);

type Ref<M, T> = <M as Acc<T>>::Pat;

trait Acc<T: ?Sized> { type Pat; }
impl<'t, T: 't + ?Sized> Acc<T> for Pure <'t> { type Pat = PureRef <'t, T>; }
impl<'t, T: 't + ?Sized> Acc<T> for Mut  <'t> { type Pat = MutRef  <'t, T>; }

struct PureRef <'t, T: ?Sized> (&'t     T);
struct MutRef  <'t, T: ?Sized> (&'t mut T);


/// USAGE ///

struct Buf<T> {
    data: Vec<T>
}

impl<T> Buf<T> {
    fn as_mut<M>(self: Ref<M, Self>) -> Ref<M, [f32]>
    where M: Acc<Self> + Acc<[f32]>,
          Ref<M, Self>: Deref<Target = Self>
    {
        unimplemented!()
    }
}

(это на самом деле компилируется)

Возможно, это скорее вопрос поддержки, но я думаю, что тем, кто зайдет на эту страницу, будет полезно понять это предложение, потому что мне не совсем понятно из RFC или комментариев здесь:

С 1.41 nightly я пробовал следующее:

pub trait MyTrait {
    type MyType<U>;

    fn f<U>(self, x : <Self as MyTrait>::MyType<U>);
}

И это не скомпилируется, ошибка «аргумент типа не разрешен» MyType .

Затем я удалил <U> , что выглядело подозрительно, но я решил попробовать:

pub trait MyTrait {
    type MyType<U>;

    fn f<U>(self, x : <Self as MyTrait>::MyType);
}

Что на удивление скомпилировалось. Но потом, когда я написал импл:

impl MyTrait for u64 {
    type MyType<U> = U;

    fn f<U>(self, x : <Self as MyTrait>::MyType) -> <Self as MyTrait>::MyType {
        x;
    }
}

Это напугало компилятор.

Мои вопросы:

  1. Возможна ли сейчас такая схема, но я просто делаю это неправильно?
  2. Если это невозможно сейчас, будет ли это когда-либо возможно в соответствии с этим предложением?
  3. Если да, то есть ли какая-нибудь идея о временной шкале, в которой я смогу сделать это ночью?

Заранее спасибо за ваше время в ответ на это.

@clintonmead Я считаю, что в конечном итоге это должно стать возможным, но реализация функции еще не завершена (действительно, компилятор предупреждает вас, что он может выйти из строя, когда вы попытаетесь его использовать!). Я не знаю, что такое временная шкала.

Может быть, стоит проконсультироваться с ребятами из Chalk, чтобы убедиться, что интеграция достаточно продвинута для возобновления работы над этой функцией?

Теперь это заблокировано

  • №30472
  • №67509
  • №67510
  • №67512
  • №67513

Можем ли мы обновить описание проблемы с помощью текущих блокировщиков?

Добавлено больше проблем с блокировкой, поднятых @DutchGhost.

Это было бы важным шагом в подражании типам более высокого рода.

Я представляю что-то вроде этого:

// the plug/unplug idea is from https://gist.github.com/edmundsmith/855fcf0cb35dd467c29a9350481f0ecf

trait Monad /* : Applicative (for pure/return, doesn't matter for this example) */ {
    // Self is like the "f a" in haskell

    /// extract the "a" from "f a"
    type Unplug;

    /// exchange the "a" in "f a" in the type of Self with B
    type Plug<B>: Monad;

    fn bind<B, F>(this: Self, f: F) -> Self::Plug<B>
    where
        F: Fn(Self::Unplug) -> Self::Plug<B>;
}

impl<A> Monad for Option<A> {
    type Unplug = A;
    type Plug<B> = Option<B>;
    fn bind<B, F>(this: Self, f: F) -> Option<B>
    where
        F: Fn(A) -> Option<B> {
        this.and_then(f)
    }
}

Вопрос по предлагаемой реализации:

У меня есть черта, которая выглядит так

trait TradeableResource{
}

и реализатор, который выглядит так

struct Food(f64);
impl TradeableResource for Food{}

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

Возможно ли это, если предлагаемая реализация рассматривается здесь?

Следующее выглядит немного странно, но, надеюсь, демонстрирует то, что я хочу сделать.

trait TradeableResource{
    type Wrapper<T>:T(f64)
}

@ChechyLevas , насколько мне известно, это не входит в рамки GADT.

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

Итак, вы можете сделать следующее:

trait Traceable resource: From<f64> + Into<f64> { }

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

Я хотел бы немного прочувствовать текущее состояние этого. Я немного поиграл с асинхронными чертами и временем жизни.
Одна вещь, которую я хотел сделать, это создать trait ReadAt<'r> со связанным типом ReadAt: Future . Это работает во многих случаях, за исключением тех случаев, когда я хочу использовать трейт-объекты.

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

impl<'a, 'r, R> ReadAt<'r> for &'a dyn for<'z> ReadAt<'z, ReadAt = R> + 'a {
    type ReadAt = R; // cannot have an `impl<R<'lifetime>>`
    ...
}

Итак, я подумал, что, может быть, это можно решить с помощью GAT, но я столкнулся с похожими проблемами, когда мне снова понадобится немного магии синтаксиса, потому что объекты типов требуют записи связанных типов:

Нравиться:

trait ReadAt {
    type ReadAt<'r>: Future<Output = io::Result<usize>>;

    fn read_at<'a>(&'a self, buf: &'a mut [u8], at: u64) -> Self::ReadAt<'a>;
}

impl<'d> ReadAt for &'d (dyn ReadAt<HERE> + 'd) {
}

Мне нужен способ включить время жизни в HERE . Это, например, принято, но IMO будет недостаточно, так как R слишком конкретен:

impl<'d, R> ReadAt for &'d (dyn ReadAt<ReadAt = R> + 'd) {
    type ReadAt<'r> = R;

    fn read_at<'a>(&'a self, buf: &'a mut [u8], at: u64) -> Self::ReadAt<'a> {
        (**self).read_at(buf, at)
    }
}

Но я не могу проверить, сработает ли это в любом случае, так как я не уверен, как превратить это в типичный объект, так как я не знаю, как получить время жизни в следующем фрагменте:

struct Test<T: ReadAt>(T);

impl<T: ReadAt> Test<T> {
    fn into_trait_object<'a>(&'a self) -> Test<&'a dyn ReadAt<ReadAt = T::ReadAt>> {
        todo!();
    }
}

Поскольку T::ReadAt занимает время жизни, которое я не могу предоставить, поэтому в этом случае не будет расширения синтаксиса для dyn ReadAt<ReadAt<'r> = T::ReadAt<'r>> . (Или для сопоставления параметров срока службы IMO приведенный выше фрагмент может просто работать ;-))

Поскольку я использую только параметры времени жизни, а не параметры типа, я думаю, что технически это должно быть возможно, если только параметры времени жизни не могут каким-то образом влиять на виртуальные таблицы и размеры типов?

@jendrikw ваш код компилируется и запускается с последней версией nightly (1.46). Я провел несколько тестов с пользовательскими типами и т. д., но мне не хватает знаний о fp, чтобы проверить больше.

Сегодня я попытался использовать GAT для чего-то, где я думал, что, возможно, время жизни будет недостаточно общим (следует рабочий код; полный файл кода ):

/// Helper trait for "stripping indention" from an object reference
pub trait AsUnindented<'ast> {
    type Output;

    /// Returns a reference to the unindented part of `Self`
    fn as_unindented(&'ast self) -> Self::Output;
}

impl<'ast, T: 'ast> AsUnindented<'ast> for Indented<T> {
    type Output = &'ast T;

    #[inline]
    fn as_unindented(&'ast self) -> &'ast T {
        &self.data
    }
}

impl<'ast, T: 'ast> AsUnindented<'ast> for crate::Block<T>
where
    T: AsUnindented<'ast> + 'ast,
{
    type Output = crate::View<'ast, T, fn(&'ast T) -> T::Output>;

    #[inline]
    fn as_unindented(&'ast self) -> Self::Output {
        crate::View::new(self, T::as_unindented)
    }
}

Затем я попытался использовать GAT в следующем коде:

pub trait AsUnindented {
    type Output<'ast>;

    /// Returns a reference to the unindented part of `Self`
    fn as_unindented<'ast>(&'ast self) -> Self::Output<'ast>;
}

impl<T> AsUnindented for Indented<T> {
    type Output<'ast> = &'ast T;

    #[inline]
    fn as_unindented<'ast>(&'ast self) -> &'ast T {
        &self.data
    }
}

impl<T> AsUnindented for crate::Block<T>
where
    T: AsUnindented,
{
    type Output<'ast> = crate::View<'ast, T, fn(&'ast T) -> T::Output<'ast>>;

    #[inline]
    fn as_unindented<'ast>(&'ast self) -> Self::Output<'ast> {
        crate::View::new(self, T::as_unindented)
    }
}

Это не работает. Он терпит неудачу с E0309 ( the parameter type 'T' may not live long enough ) для обеих реализаций типажа. Я не уверен, как это исправить, или у меня есть какое-то неправильное представление. Компилятор хочет, чтобы я установил ограничение на T на impl , но на этом уровне нет времени жизни 'ast , и я не хочу ограничивать T: 'static (и что-то вроде for<'ast> T: 'ast не работает).

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

Связанное время жизни может быть добавлено к связанному типу (используя Self: 'ast в версии трейта). Этот тест показывает, как это должно выглядеть:
https://github.com/rust-lang/rust/blob/db4826dd6ca48663a0b4c5ab0681258999017c7d/src/test/ui/generic-associated-types/iterable.rs#L6 -L21

ну это работает только частично...


Сообщения об ошибках

error[E0309]: the parameter type `T` may not live long enough
  --> src/indention.rs:33:5
   |
33 |     type Output<'ast> where T: 'ast = &'ast T;
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = help: consider adding an explicit lifetime bound `T: 'ast`...
   = note: ...so that the type `T` will meet its required lifetime bounds

error[E0309]: the parameter type `T` may not live long enough
  --> src/indention.rs:45:5
   |
45 |     type Output<'ast> where T: 'ast = crate::View<'ast, T, fn(&'ast T) -> T::Output<'ast>>;
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = help: consider adding an explicit lifetime bound `T: 'ast`...
   = note: ...so that the type `T` will meet its required lifetime bounds

error[E0309]: the parameter type `T` may not live long enough
  --> src/indention.rs:59:5
   |
59 | /     type Output<'ast2>
60 | |         where
61 | |             T: 'ast2,
62 | |             O: 'ast2
63 | |         = crate::View<'ast2, T, crate::view::MapViewFn<F, fn(F::Output<'ast2>) -> O::Output<'ast2>>>;
   | |_____________________________________________________________________________________________________^
   |
   = help: consider adding an explicit lifetime bound `T: 'ast2`...
   = note: ...so that the type `T` will meet its required lifetime bounds

error[E0309]: the parameter type `O` may not live long enough
  --> src/indention.rs:59:5
   |
59 | /     type Output<'ast2>
60 | |         where
61 | |             T: 'ast2,
62 | |             O: 'ast2
63 | |         = crate::View<'ast2, T, crate::view::MapViewFn<F, fn(F::Output<'ast2>) -> O::Output<'ast2>>>;
   | |_____________________________________________________________________________________________________^
   |
   = help: consider adding an explicit lifetime bound `O: 'ast2`...
   = note: ...so that the type `O` will meet its required lifetime bounds

Вроде как код проходит одну стадию (ранее выдавал ошибки с похожими ошибками, но между ними появилась другая категория ошибок, состоящая только из E0107 s) хм.

edit : я пропустил первое утверждение ( where Self: 'ast ). Теперь эта часть исправлена. Я пытаюсь продолжить.


дополнительный контекст

хорошо, следующий фрагмент:

impl<'ast, T, F, O> AsUnindented for crate::View<'ast, T, F>
where
    Self: Clone,
    F: crate::view::ViewFn<T, Output = &'ast O>,
    O: AsUnindented + 'ast,
{
    type Output<'ast2>
        where
            T: 'ast2,
        = crate::View<'ast, T, crate::view::MapViewFn<F, fn(&'ast O) -> O::Output<'ast>>>;

    #[inline]
    fn as_unindented<'ast2>(&'ast2 self) -> Self::Output<'ast2> {
        self.clone().map::<for<'x> fn(&'x O) -> O::Output<'x>, _>(O::as_unindented)
    }
}

ошибки с:

error[E0631]: type mismatch in function arguments
  --> src/indention.rs:66:67
   |
66 |         self.clone().map::<for<'x> fn(&'x O) -> O::Output<'x>, _>(O::as_unindented)
   |                                                                   ^^^^^^^^^^^^^^^^
   |                                                                   |
   |                                                                   expected signature of `for<'x> fn(<F as view::ViewFn<T>>::Output<'x>) -> _`
   |                                                                   found signature of `for<'x> fn(&'x O) -> _`

и я знаю, что <F as view::ViewFn<T>>::Output<'x> ==> &'ast O и &'x O не равны, но я не знаю, как это исправить (компилятор не принимает привязку F: for<'x> crate::view::ViewFn<T, Output = &'x O> ).
Другие ошибки попытки с:

error[E0582]: binding for associated type `Output` references lifetime `'x`, which does not appear in the trait input types
  --> src/indention.rs:56:39
   |
56 |     F: for<'x> crate::view::ViewFn<T, Output = &'x O>,
   |                                       ^^^^^^^^^^^^^^

Я не знаю, как выразить привязку forall<'x> в назначении типа вывода типажей, например

where
    F: crate::view::ViewFn<T, for<'x> Output<'x> = &'x O>,

даже не анализирует («ожидаемый один из + , , , :: или > , найдено = »).

67510 треков, которые можно записать с привязкой. В этом примере также может потребоваться ленивая нормализация (#60471).

Является ли тип вывода типа self.clone().map действительно типом O::as_unindented , то есть типом аргумента карты
Не знаю, что такое crate::View , но функция map может ожидать другой аргумент, поэтому несоответствие типов.

@sighoya Я связал репозиторий в предыдущих сообщениях, вот прямая ссылка на внедрение crate::view::ViewFn::map

Что по этому поводу говорит компилятор?:

where
    for<'x> F: crate::view::ViewFn<T, Output<'x> = &'x O>,

Он не анализирует (пока).

@matthewjasper ,

Из Rust Nomicon следующие парсинги:

where for<'a> F: Fn(&'a (u8, u16)) -> &'a u8,

Это из-за использования Output<'x> = &'x 0> он не анализирует?

Да, Output<'x>=... не анализируется в этой позиции.

У меня есть create, grdf , который больше не компилируется с rustc 1.46.0-nightly . Изменилось ли что-нибудь вокруг GAT в последнее время?

Мой случай немного странный, так как мне пришлось использовать трюк, чтобы заставить его работать. У меня есть черта Graph со связанными типами итераторов. Чтобы это работало, я поместил все итераторы в другой трейт, Iter :

pub trait Iter<'a, T: 'a> {
    type Triples: Iterator<Item = Triple<&'a T>>;
    type Subjects: Iterator<Item = (&'a T, Self::Predicates)>;
    type Predicates: Iterator<Item = (&'a T, Self::Objects)>;
    type Objects: Iterator<Item = &'a T>;
}

pub trait Graph<T = crate::Term> {
    /// Iterators.
    type Iter<'a>: Iter<'a, T>;

    ...
}

У меня есть одна реализация Graph , которая до сих пор работала нормально:

impl<'a, T: 'a + Hash + Eq> crate::Iter<'a, T> for Iterators {
    type Objects = Objects<'a, T>;
    type Predicates = Predicates<'a, T>;
    type Subjects = Subjects<'a, T>;
    type Triples = Iter<'a, T>;
}

impl<T: Hash + Eq> crate::Graph<T> for HashGraph<T> {
    type Iter<'a> = Iterators;

    ...
}

Теперь, когда я обновил rustc, произошел сбой со следующим:

error[E0309]: the parameter type `T` may not live long enough
  --> src/hash_dataset.rs:50:2
   |
50 |     type Iter<'a> = Iterators;
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = help: consider adding an explicit lifetime bound `T: 'a`...
   = note: ...so that the type `T` will meet its required lifetime bounds

Для меня это не имеет особого смысла, поскольку T уже связан с 'a в Iter ...

Хорошо, я снова заставил его работать, добавив несколько границ where .
В черте:

type Iter<'a>: Iter<'a, T> where T: 'a;

и в реализации:

type Iter<'a> where T: 'a = Iterators;

Но я не уверен, что понимаю точную семантику границ where , особенно в реализации (впервые такое вижу). А также почему это раньше работало.

РЕДАКТИРОВАТЬ: я даже смог удалить свой неприятный хак и поместить связанные итераторы в сам признак.

Добавлено больше проблем с блокировкой, поднятых @DutchGhost.

Вы также можете добавить https://github.com/rust-lang/rust/issues/74684 :)

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

Где мы с этим? Самая последняя сводка статуса здесь сделана @LukasKalbertodt за июнь 2018 года . Ждет ли "меления" ?

Наивное наблюдение: почти все мои желаемые варианты использования GAT требуют только одного параметра времени жизни. Будет ли проще предоставить урезанную пожизненную версию GAT?

https://github.com/rust-lang/rust/issues/44265#issuecomment -568247656 — это (несколько краткое) обновление.

67510 — это последняя важная функция ICE/отсутствующая, которую необходимо реализовать.

Сделает ли этот RFC Monad и Functor возможными напрямую? Или над HKT нужно еще поработать?

Сделает ли этот RFC Монаду и Функтор возможными напрямую? Или над HKT нужно еще поработать?

@ibraheemdev RFC заявляет

Это не добавляет всех функций, которые люди хотят, когда говорят о типах более высокого рода. Например, он не включает такие черты, как Monad. Некоторые люди могут предпочесть реализовать все эти функции одновременно. Однако эта функция совместима с другими видами полиморфизма более высокого рода и никоим образом не препятствует их реализации. На самом деле, он прокладывает путь, решая некоторые детали реализации, которые повлияют и на другие виды высшей доброты, такие как частичное применение.

@ibraheemdev : мне кажется, что самый идиоматический способ сделать монады и функторы возможными — это ввести общие связанные черты , но общие связанные типы, безусловно, являются необходимым условием.

Есть ли путь к Monad , который не требует GAT?

@ibraheemdev Гипотетически да, можно было бы реализовать HKT напрямую, а затем реализовать трейт Monad сверху. Однако работы в этом направлении не ведется и, вероятно, никогда не будет, потому что такой подход на самом деле не решает проблем, с которыми сталкивается Rust: https://twitter.com/withoutboats/status/1027702531361857536 .

Может быть, я ошибаюсь, но я думаю, что GAT позволяет что-то вроде

trait MonadFamily {
    type Monad<T>;
    fn pure<T>(inner: T) -> Self::Monad<T>;
    fn bind<T, U, F: FnOnce(T) -> U>(this: Self::Monad<T>, f: F) -> Self::Monad<U>;
}

@ibraheemdev

Есть ли путь к Monad , который не требует GAT?

Ага, см. Метод эмуляции типов более высокого порядка в Rust . Он даже работает на стабильной сейчас.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги