Противоречивые реализации трейта в Rust

Я хочу реализовать собственный трейт для &'a str и для целых чисел до i32, но Rust не позволяет мне:

use std::convert::Into;

pub trait UiId {
    fn push(&self);
}

impl<'a> UiId for &'a str {
    fn push(&self) {}
}

impl<T: Into<i32>> UiId for T {
    fn push(&self) {}
}

fn main() {}

Это не может быть скомпилировано со следующей ошибкой:

error[E0119]: conflicting implementations of trait `UiId` for type `&str`:
  --> src/main.rs:11:1
   |
7  | impl<'a> UiId for &'a str {
   | ------------------------- first implementation here
...
11 | impl<T: Into<i32>> UiId for T {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `&str`
   |
   = note: upstream crates may add new impl of trait `std::convert::From<&str>` for type `i32` in future versions

&'a str не реализует Into<i32>. Можно ли реализовать UiId для &'a str и всего, что можно преобразовать в i32, без указания конкретных типов? Как я могу это сделать?


person Slava Baginov    schedule 26.08.2016    source источник
comment
Я думаю, что это должно быть ограничение в том, как Rust определяет, накладываются ли реализации (или могут ли они перекрываться), но я не нашел нигде, где прописаны правила. :-( Ой, для правильной языковой спецификации!   -  person Chris Emerson    schedule 26.08.2016


Ответы (2)


Тот факт, что &'a str не реализует Into<i32>, не принимается во внимание, потому что нет гарантии, что это не может быть добавлено позже. Тогда это нарушит ваш код.

Так что, если бы это было разрешено, возможная поломка затруднила бы добавление реализаций в свойства библиотеки.

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

Лучшее, что я смог найти, - это RFC 1023, в котором говорится, что ящик [...] не может полагаться на то, что Type: !Trait удерживается, если Type или Trait не являются локальными.

person starblue    schedule 26.08.2016
comment
Я вообще-то не думаю, что это правда. Разве не для этого нужны сиротские правила? Вы не можете добавить чужую черту impls для чужого типа. В текущем ящике должна быть определена по крайней мере одна характеристика и тип ... чтобы компилятор мог рассуждать о подобных случаях, я думаю. - person Lukas Kalbertodt; 26.08.2016
comment
@LukasKalbertodt См. Мою редакцию. Он находится в том же предложении, что и правило ophan в RFC 1023. - person starblue; 26.08.2016
comment
О, действительно, это звучит разумно. Спасибо! - person Lukas Kalbertodt; 26.08.2016

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

Под маркерной чертой стоит Numeric.

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

Признак Numeric должен быть pub, потому что они предупреждают, что в будущих версиях будет запрещен частный признак в общедоступном интерфейсе.


use std::convert::Into;

pub trait Numeric {}
impl Numeric for f64 {}
impl Numeric for f32 {}
impl Numeric for i64 {}
impl Numeric for i32 {}
impl Numeric for i16 {}
impl Numeric for i8 {}
impl Numeric for isize {}
impl Numeric for u64 {}
impl Numeric for u32 {}
impl Numeric for u16 {}
impl Numeric for u8 {}
impl Numeric for usize {}


pub trait UiId {
    fn push(&self);
}

impl<'a> UiId for &'a str {
    fn push(&self) {}
}

impl<T: Into<i32> + Numeric> UiId for T {
    fn push(&self) {}
}

person Paul Chernoch    schedule 08.03.2021