Это проблема отслеживания общих связанных типов (rust-lang/rfcs#1598).
СДЕЛАТЬ:
Вот своего рода план реализации, который я буду стараться обновлять.
Позвольте мне начать с более подробного описания AST. Сначала давайте обсудим, как это работает сегодня :
Элемент type Foo: Bar [= Baz];
в определении признака определяется этим вариантом AST . Это включает в себя границы ( Bar
) и (необязательное) значение по умолчанию Baz
. Имя определяется в структуре TraitItem
.
Элемент type Foo = Bar;
в реализации признака определяется этим вариантом AST , который включает только тип Bar
, потому что Foo
и т. д. определен в ImplItem
структура .
Методы представляют собой интересный случай, поскольку их уже можно сделать универсальными. Эти общие параметры объявляются в поле Generics
структуры MethodSig
. Это экземпляр структуры Generics
.
Я считаю, что лучше всего было бы «поднять» Generics
из методов в TraitItem
(и ImplItem
), чтобы это в равной степени применялось ко всем формам трейты и импл элементы. На данный момент мы не будем поддерживать общие константы, я думаю, но, честно говоря, они, вероятно, просто выпадают из работы, которую мы делаем в любом случае, поэтому они будут небольшим расширением. Я думаю, что работа пойдет лучше, если мы просто запланируем их сейчас.
Возможно, достойным первым пиаром было бы просто внести это изменение , сохранив все остальные существующие функции прежними. То есть мы увеличили бы Generics
до TraitItem
(и ImplItem
) и из 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)
В общем, моделирование того, как работают методы, — неплохая идея. Я полагаю, мы могли бы также сделать здесь небольшую «проверку на будущее».
Вот код, который переносит параметры типа метода в область видимости (это для метода, определенного в трейте):
В то время как для type
, определенного в свойстве, мы жестко запрограммированы на добавление пустого параметра типа rib ( NoTypeParameters
):
Теперь, когда дженерики присутствуют в каждом элементе типаж/имплементации, я думаю, мы, вероятно, захотим удалить обработку для type
и извлечь обработку метода, чтобы она происходила на более высоком уровне. Для элементов (например, const
), где нет дженериков, вновь введенное ребро должно быть пустым и, следовательно, безвредным (надеюсь).
Другие интересные места:
Вы поняли идею.
@petrochenkov -- звучит примерно так?
@никомацакис
звучит примерно так?
Все выглядит правильно.
Следующий шаг. Пожизненное разрешение.
К лучшему или к худшему, в настоящее время это делается в совершенно отдельном фрагменте кода от другого разрешения имен. Это потому, что это происходит после создания HIR. Почти наверняка это изменится, но пока не изменилось.
Основные идеи такие же, как и в обычном разрешении имен, за исключением того, что мы называем вещи не «ребрами», а «областями». знак равно
Есть некоторые легкие осложнения из-за этой концепции «поздних» жизней. Однако это не очень важно здесь - все времена жизни для универсального ассоциированного типа будут "ранними связями", что является довольно простым случаем. Время жизни с «поздней привязкой» — это время жизни, объявленное для метода или функции, значение которой не предоставляется до тех пор, пока метод не будет вызван. Я не буду вдаваться в подробности здесь, потому что это не так важно — главное, что мы не хотим следовать точно той же модели для методов, что и для других видов универсальных элементов, в отличие от другого разрешения имен. случаи.
Вот пример кода. Это код, который посещает impl
, struct
или другой нефункциональный элемент. В этих случаях, как и в GAT, мы в основном хотим перенести все параметры времени жизни из Generics
в область действия и сопоставить их с временем жизни с «ранней привязкой»:
Вы можете видеть, что он сначала создает вектор жизней, вызывая Region::early
для каждого из них:
Затем он создает Scope
. Переменная next_early_index
просто подсчитывает, сколько жизней с ранней привязкой находится в области видимости. Поскольку этот код специфичен для элементов, это всегда будет число ранних жизней, объявленных для этого текущего элемента. (Позже мы рассмотрим случай, когда мы вводим в область действия дополнительные времена жизни, что больше всего нам нужно для GAT.)
Наконец, мы вызываем with()
. Этот метод перенесет область в область видимости и вызовет закрытие. Любые воплощения, которые мы посещаем внутри этого закрытия, будут видеть те имена, которые мы только что определили как находящиеся в области видимости:
Хорошо, теперь давайте рассмотрим еще один пример. Этот случай охватывает "черты реализации". Детали того, что он делает, не так уж важны (то есть вам не нужно выполнять дешугаринг на impl Trait
как таковой). Достаточно сказать, что это вводит в область видимости некоторые новые ранние сроки жизни — это именно то, что мы собираемся сделать для GAT.
Позвольте мне выделить несколько вещей. Во-первых, метод next_early_index
возвращает следующий неназначенный ранний индекс:
Это обеспечивает отправную точку. Затем мы снова используем Region::early
для создания новых определений времени жизни с ранней привязкой, которые будут разрешены:
Наконец, мы вносим их в область видимости, снова вызывая with
:
Хорошо, это два примера. Мы собираемся сделать что-то очень похожее на второй. Мы хотим изменить определения этих двух методов:
Оба из них должны будут для связанных типов обрабатывать связанные дженерики. (Возможно, в других случаях они должны утверждать, что дженерики пусты.)
Так что я вскочил на это ранее сегодня. Я хотел бы убедиться, что я на правильном пути:
qpath_to_ty
и associated_path_def_to_ty
в librustc_typeck/astconv.rs
, чтобы исправить ошибки type parameters are not allowed on this type
. Я думаю, что это нужно заменить некоторыми проверками. Также...сбои 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
-- выдает неожиданную ошибку E0110empty_generics
-- проверяет, что type Bar<,>
выдает ошибку, все в порядкеgeneric-associated-types-where.rs
-- проверяет, можем ли мы разобрать предложения where
в нужных местах, кажется, все в порядкеgeneric_associated_type_undeclared_lifetimes.rs
-- выдает неожиданную ошибку E0110iterable.rs
-- выдает неожиданную ошибку E0110pointer_family.rs
-- выдает неожиданную ошибку E0109streaming_iterator.rs
-- выдает неожиданную ошибку E0110pointer_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>
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 сообщает prohibit_type_params
:
Первый шаг — выяснить, откуда это вызывается. Мой предпочтительный способ сделать это — получить локальную сборку и использовать -Ztreat-err-as-bug
сочетании с RUST_BACKTRACE=1
. Но я не могу показать вам эти результаты, потому что rustc все еще находится в процессе сборки. :P Так что вместо этого я сделал быстрый rg prohibit_type_params
, позвольте мне быстро указать на один подозрительный случай, который я вижу.
Один вызов из associated_path_def_to_ty
:
Как указано в комментарии, это вызывается для разрешения компонента Pointer<T>
в пути, подобном Self::Pointer<T>
(обратите внимание, что параметры типа считаются частью сегмента пути вместе с именем, к которому они присоединены ). До GAT параметры типа были недопустимы (например, T::Item
), поэтому у нас просто есть общее ограничение:
Ясно, что этого не будет. Мы должны удалить эту строку и заменить ее какой-либо проверкой того, что, если параметры указаны, они соответствуют ожидаемому числу. Для этого нам, вероятно, нужен код проверки ошибок, подобный тому, что находится в 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. Итак, я попытаюсь обобщить всю информацию об этой функции; возможно, это полезно для некоторых людей. Но я ничего не знаю, так что отнеситесь к этому с недоверием! Также: пожалуйста, сообщите мне, если какая-то информация неверна, чтобы я мог это исправить.
@sunjay получил два замечательных PR:
Потом какое-то время мало что происходило. Затем Нико резюмировал, что еще должно произойти в этом комментарии (2018-03-12). В основном (а) писать тесты, (б) исправлять ошибки/улучшать синтаксический анализатор/интерфейс и (в) реализацию в системе трейтов. Пункт (а) был дополнительно объяснен в этом комментарии (2018-03-13).
@gavento приземлился Расширенные тесты для RFC1598 (GAT) (объединено 11 мая 2018 г.)
С тех пор никаких тестов пользовательского интерфейса не добавлялось . И я не могу найти никаких других 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 в компилятор? (Возможно, для этого есть отдельная тема.)
Возможно, для этого есть отдельная тема.
Как примечание (возможно, известное), этот код вызывает панику у компилятора:
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;
}
}
Это напугало компилятор.
Мои вопросы:
Заранее спасибо за ваше время в ответ на это.
@clintonmead Я считаю, что в конечном итоге это должно стать возможным, но реализация функции еще не завершена (действительно, компилятор предупреждает вас, что он может выйти из строя, когда вы попытаетесь его использовать!). Я не знаю, что такое временная шкала.
Может быть, стоит проконсультироваться с ребятами из Chalk, чтобы убедиться, что интеграция достаточно продвинута для возобновления работы над этой функцией?
Теперь это заблокировано
Можем ли мы обновить описание проблемы с помощью текущих блокировщиков?
Добавлено больше проблем с блокировкой, поднятых @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>,
даже не анализирует («ожидаемый один из +
, ,
, ::
или >
, найдено =
»).
Является ли тип вывода типа 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 — это (несколько краткое) обновление.
Сделает ли этот 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 . Он даже работает на стабильной сейчас.
Самый полезный комментарий
https://github.com/rust-lang/rust/issues/44265#issuecomment -568247656 — это (несколько краткое) обновление.
67510 — это последняя важная функция ICE/отсутствующая, которую необходимо реализовать.