Почему мой параметр типа в этом блоке impl не ограничен?

Надеюсь, это простой вопрос для мастеров дженериков Rust. Я хочу написать подразумеваемый Display для всего, что можно позаимствовать как мой тип Offset (например, Offset, &Offset, Box<Offset> и т. д.). И придумали следующее:

impl<O, C> Display for Modification<O>
where
    O: Borrow<Offset<C>>,
    C: Display,

Я считаю, что проблема здесь в том, что моя Offset структура имеет параметр типа, и этот параметр необходимо реализовать Display, чтобы в целом Modification<O> это можно было сделать.

Как есть, этот код генерирует ошибку:

error[E0207]: the type parameter `C` is not constrained by the impl trait, self type, or predicates
  --> crates/polychem/src/polymers/modification.rs:42:9
   |
42 | impl<O, C> Display for Modification<O>
   |         ^ unconstrained type parameter

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

impl<O, C> Display for Modification<O>
where
    O: Borrow<C>,
    C: Display,

Таким образом, добавление этой общей структуры в смесь либо (1) выявляет ограничения в решении признаков в Rust, либо — что гораздо более вероятно — (2) действительно является ничем не ограниченным, но в каком-то смысле, который я сейчас не понимаю. ..

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

Как создавать пользовательские общие типы в Python (50/100 дней Python)
Как создавать пользовательские общие типы в Python (50/100 дней Python)
Помимо встроенных типов, модуль типизации в Python предоставляет возможность определения общих типов, что позволяет вам определять типы, которые могут...
0
0
55
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Это неограниченно.

Rust требует, чтобы данная черта была реализована для типа не более одного раза. (Это называется когерентностью.) Однако этот блок impl допускает бесконечное количество реализаций Display для одного экземпляра Modification<O> путем изменения типа C.

Поскольку Borrow является универсальным, конкретный тип, представленный O, может реализовать Borrow<Offset<A>> и Borrow<Offset<B>> для разных типов A и B. Какой из них выбрать блоку impl? Не существует четкого способа принятия решения, поэтому это impl запрещено.

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

Нет, это тоже не получается:

error[E0207]: the type parameter `C` is not constrained by the impl trait, self type, or predicates
 --> src/lib.rs:6:9
  |
6 | impl<O, C> Display for Modification<O>
  |         ^ unconstrained type parameter

Один из способов решить эту проблему — создать признак, который использует связанный тип вместо универсального для указания «канонического» типа смещения:

trait BorrowOffset: Borrow<Offset<Self::OffsetType>> {
    type OffsetType;
}

Теперь вы можете ссылаться на этот связанный тип в реализации Display для Modification<O> вместо использования универсального типа impl:

impl<O> Display for Modification<O>
where
    O: BorrowOffset,
    O::OffsetType: Display,
{
    // ...
}

Спасибо за быстрый ответ! Я могу понять, как это нарушает правила связности Rust, хотя я не уверен, что полностью понимаю обоснование «какой из блоков impl должен выбрать», поскольку все возможные impl одинаковы? Хотя я полагаю, что после мономорфизации у вас могут возникнуть различия? Кроме того, кажется странным, что признак Borrow в std не использует связанный тип... Документация AsRef ясно дает понять, что Borrow на самом деле не следует использовать для возврата каких-либо полей, поскольку принадлежащие и заимствованные представления должны быть Eq . В любом случае, спасибо за быструю проверку здравомыслия!

Brooks 02.04.2024 00:25

По сути, было бы неплохо иметь такую ​​черту, как Deref, в стандартном стандарте, в котором есть намек на одеяло из Borrow: impl<T> Deref<Target = T> for T

Brooks 02.04.2024 00:32

Я не уверен, что понимаю, при чем здесь Deref. «хотя я не уверен, что полностью понимаю обоснование «какой из блоков impl выбрать», поскольку все возможные impl будут одинаковыми» — у них будет один и тот же код, но разное значение. Если O реализует Borrow<Offset<A>> и Borrow<Offset<B>>, то реализация может использовать либо реализацию ADisplay, либо реализацию B. Можете ли вы сказать мне, какой из них следует выбрать и почему?

cdhowie 02.04.2024 02:02

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

Brooks 02.04.2024 19:22

@Brooks Вполне возможно, что вы могли бы предложить два разных заимствования, которые сравниваются как равные так же, как и базовое значение, поэтому я не думаю, что здесь есть конфликт. Рассмотрим PathBuf, который реализует Borrow<Path> — концептуально он также мог бы реализовать Borrow<str>, если бы не тот факт, что PathBuf может представлять недопустимые последовательности UTF-8, а &str — нет. Я согласен, что было бы проще использовать связанный тип, но я думаю, что есть случаи, когда имеет смысл реализовать это для разных типов.

cdhowie 02.04.2024 20:53

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

cdhowie 02.04.2024 20:54

Другие вопросы по теме