Надеюсь, это простой вопрос для мастеров дженериков 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) действительно является ничем не ограниченным, но в каком-то смысле, который я сейчас не понимаю. ..
Будем рады предоставить дополнительную информацию и контекст, если это будет сочтено полезным!

Это неограниченно.
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,
{
// ...
}
По сути, было бы неплохо иметь такую черту, как Deref, в стандартном стандарте, в котором есть намек на одеяло из Borrow: impl<T> Deref<Target = T> for T
Я не уверен, что понимаю, при чем здесь Deref. «хотя я не уверен, что полностью понимаю обоснование «какой из блоков impl выбрать», поскольку все возможные impl будут одинаковыми» — у них будет один и тот же код, но разное значение. Если O реализует Borrow<Offset<A>> и Borrow<Offset<B>>, то реализация может использовать либо реализацию ADisplay, либо реализацию B. Можете ли вы сказать мне, какой из них следует выбрать и почему?
Я вижу, что здесь неоднозначность значений черт! Я также понял, что отчасти причина моего замешательства заключалась в том, что эти вещи, казалось, работали для общих функций, но, конечно, это потому, что им не нужно придерживаться этих правил согласованности, поскольку вы можете устранить неоднозначность, используя турборыбу. Я думаю, что конкретная проблема с заимствованием заключается в том, что семантически я не думаю, что у вас когда-либо должны быть обе эти опции (поскольку Eq и т. д. должны быть одинаковыми для собственной версии). Я думаю (насколько я могу судить), что в Borrow было бы разумнее иметь связанный тип, такой как Deref!
@Brooks Вполне возможно, что вы могли бы предложить два разных заимствования, которые сравниваются как равные так же, как и базовое значение, поэтому я не думаю, что здесь есть конфликт. Рассмотрим PathBuf, который реализует Borrow<Path> — концептуально он также мог бы реализовать Borrow<str>, если бы не тот факт, что PathBuf может представлять недопустимые последовательности UTF-8, а &str — нет. Я согласен, что было бы проще использовать связанный тип, но я думаю, что есть случаи, когда имеет смысл реализовать это для разных типов.
@Brooks Несмотря на это, вы попали в самую точку с точки зрения понимания проблемы. Использование вашей собственной черты со связанным типом (как предложено в моем ответе), вероятно, самый быстрый способ решить вашу проблему, хотя есть и другие подходы, которые также могут сработать.
Спасибо за быстрый ответ! Я могу понять, как это нарушает правила связности Rust, хотя я не уверен, что полностью понимаю обоснование «какой из блоков
implдолжен выбрать», поскольку все возможныеimplодинаковы? Хотя я полагаю, что после мономорфизации у вас могут возникнуть различия? Кроме того, кажется странным, что признакBorrowв std не использует связанный тип... ДокументацияAsRefясно дает понять, чтоBorrowна самом деле не следует использовать для возврата каких-либо полей, поскольку принадлежащие и заимствованные представления должны бытьEq. В любом случае, спасибо за быструю проверку здравомыслия!