Несколько имплементаций некоторого trait, для одного и того же типа, в разных модулях - вызывают ошибку компилятора conflicting implementation for

Тех.вопросы
Aug 31, 2018
C
struct Bar(i32);

mod a {
    pub fn same_name() {} ///  isolated

    use ::std::io::*;
    impl Read for super::Bar { //clash
        fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
            Ok(0)
        }
    }
}

mod b {
    pub fn same_name() {} ///  isolated

    use ::std::io::*;
    impl Read for super::Bar {  //clash
        fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
            Ok(0)
        }
    }
}

fn main() {
}

пример рафинированный.

два модуля a и b
с одинаковым содержимым.

функции с одинаковыми именами s в модулях изолируются и все с ними хорошо

а имплементация trait Read НЕ изолируется ??? ошибка вида

> error[E0119]: conflicting implementations of trait std::io::Read for type Bar:
> --> src\main.rs:20:5
>    |
>  8 | impl Read for super::Bar {
>    | ------------------------ first implementation here
> ...
> 20 | impl Read for super::Bar {
>    | ^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for Bar
> 
> error: aborting due to previous error
> 
> For more information about this error, try rustc --explain E0119.
> error[E0119]: conflicting implementations of trait std::io::Read for type Bar:
> --> src\main.rs:20:5
>    |
>  8 | impl Read for super::Bar {
>    | ------------------------ first implementation here
> ...
> 20 | impl Read for super::Bar {
>    | ^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for Bar

всю голову сломал. выручайте.

запилил в багрепорт. будем посмотреть…

O

Да вроде все штатно - нельзя дважды для одного типа один и тот же типаж реализовать.

Модули тут не понятно как помочь должны. Ты же потом в другом месте (вообще вне модулей a и b) импортируешь Bar и импортируешь Read - откуда компилятору знать какую из реализаций надо использовать?

Это просто из интереса вопрос? Или какую-то задачу этим пытался решить?

C
Replying to #2

откуда компилятору знать какую из

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

вот с функциям, с одинаковыми именами, но в разных модулях, которые их изолируют, он, молодец, справился.

Это просто из интереса вопрос? Или

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

В BlackBox отправка и получение данных в виде потока байт происходит через Channel.
Под подобную задачу во всех языках создана инфраструктура позволяющая выстраивать цепочки обработки получаемых/отправляемых данных.

В Java это java.io.InputStream / java.io.OutputStream
в С# - System.IO.Stream …

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

в Rust я ожидал увидеть подобное, и увидел,
::std::io::Read
::std::io::Write

попытался имплементировать … и даже все получилось.
НО(!)
В BlackBox существует два типа Channel .
Один продвинутый - с сжатием данных и байт стаффом и вычислением контрольной суммы, для работы по голому USART например по радиоканалу управления квадрокоптером.

Другой простой - только со сжатием данных. Когда зашита от ошибок реализована на нижележащем уровне Ethernet например.

В Java и C# я эти варианты реализовал наследованием, В Rust соответственно подобного наследования у структур нет.

Соответственно я пытаюсь сделать две реализации Read и Write для простого и продвинутого случая.

откуда компилятору знать какую из реализаций надо использовать?

Предполагалось положить эти имплементации в разные модули.

Когда необходима простая отправку/получения - импортируем модуль с простой реализацией, когда продвинутая - с другой.

C функциями работает

mod a {
    pub fn same_name() { println!("mod a") }
}

mod b {
    pub fn same_name() { println!("mod b") }
}

fn main() {
    {
        use a::*;
        same_name();
    }
    {
        use b::*;
        same_name();
    }
}

mod a
mod b

Process finished with exit code 0

Ожидал подобной диспетчеризации и в случае имплементации.
По моему логично.
Как минимум меня удивляет отсутствие межмодульной изоляции.

Да вроде все штатно

за ссылочку был бы благодарен.

O
Replying to #3

за ссылочку был бы благодарен.

Затрудняюсь дать ссылку прям вот на конкретный ответ “почему нельзя дважды реализовать один и тот же типаж для одного и того же типа”, оно просто логически вытекает из того как работают типажи: Traits - The Rust Programming Language

Ну и может само описание ошибки еще: Rust error codes index - Error codes index

Ожидал подобной диспетчеризации и в случае имплементации.

Реализации типажей глобальны. Если бы модуля b не было, то ты бы мог импортировать в другой модуль сам типаж и саму структуру и сразу использовать факт реализации одного для другого:

mod c {
   use Bar;
   use std::io::Read;

    let mut b = Bar(0);
    b.read(.....);
}

И тут не важно где сама реализация была.

C функциями работает

Да, потому что в месте вызова функции тебе нужно просто что бы путь до нее был однозначен. Сделал нужный use и все.

Как минимум меня удивляет отсутствие межмодульной изоляции.

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

Соответственно я пытаюсь сделать две реализации Read и Write для простого и продвинутого случая.

А просто два типажа сделать не вариант?

#[derive(Debug)]
struct Bar(pub u8);

mod a {
    use std::io;
    use Bar;

    pub trait A {
        fn read(&mut self, buf: &mut [u8]) -> io::Result<usize>;
    }

    impl A for Bar {
        fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
            buf[0] = 1;
            buf[1] = self.0;
            Ok(0)
        }
    }
}

mod b {
    use std::io;
    use Bar;
    
    pub trait B {
        fn read(&mut self, buf: &mut [u8]) -> io::Result<usize>;
    }

    impl B for Bar {
        fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
            buf[0] = 2;
            buf[1] = self.0;
            Ok(0)
        }
    }
}

fn use_a() {
    use a::A;

    let mut bar = Bar(1);
    let mut buf = [0; 4];
    bar.read(&mut buf).unwrap();
    println!("a: bar={:?}, buf={:?}", bar, buf);
}

fn use_b() {
    use b::B;

    let mut bar = Bar(2);
    let mut buf = [0; 4];
    bar.read(&mut buf).unwrap();
    println!("b: bar={:?}, buf={:?}", bar, buf);
}

fn main() {
    use_a();
    use_b();
}

Playground

UPD: На всякий добавлю еще что если для Bar реализовать io::Read и отнаследовать типажи от него же, то можно будет переопределять и использовать методы io::Read в A и B.

C
Replying to #4

Во первых большое спасибо за оперативные и полноценные ответы.
Я их внимательно поизучаю…

а пока я сделал вот так.

extern "C" {
    pub fn input_bytes(ch: *mut Channel, dst: *mut u8, dst_bytes: usize) -> u32;
}
extern "C" {
    pub fn output_bytes(ch: *mut Channel, src: *const u8, src_bytes: usize);
}
extern "C" {
    pub fn input_bytes_adv(ch: *mut Channel, dst: *mut u8, dst_bytes: usize) -> u32;
}
extern "C" {
    pub fn output_bytes_adv(ch: *mut Channel, src: *const u8, src_bytes: usize);
}

#[repr(C)]
pub struct Channel {
    pub time: u32,
    pub b2p: Channel_Flow,
    pub p2b: Channel_Flow,
    pub process: ::std::option::Option<
        unsafe extern "C" fn(pack: *mut PackBytes, id: i32) -> *mut PackBytes,
    >,
    pub bits: u16,
}


impl ::std::io::Read for Channel {
    fn read(&mut self, dst: &mut [u8]) -> Result<usize, ::std::io::Error> {
        unsafe { Ok(input_bytes(self, dst.as_mut_ptr(), dst.len()) as usize) }// input_bytes !!
    }
}

impl ::std::io::Write for Channel {
    fn write(&mut self, src: &[u8]) -> Result<usize, ::std::io::Error> {
        unsafe { output_bytes(self, src.as_ptr(), src.len()); }
        Ok(src.len() as usize)
    }
    
    fn flush(&mut self) -> Result<(), ::std::io::Error> {
        Ok(())
    }
}

struct AdvChannel(Channel);

impl ::std::io::Read for AdvChannel {
    fn read(&mut self, dst: &mut [u8]) -> Result<usize, ::std::io::Error> {
        unsafe { Ok(input_bytes_adv(transmute(self), dst.as_mut_ptr(), dst.len()) as usize) } //input_bytes_adv !!
    }
}

impl ::std::io::Write for AdvChannel {
    fn write(&mut self, src: &[u8]) -> Result<usize, ::std::io::Error> {
        unsafe { output_bytes_adv(transmute(self), src.as_ptr(), src.len()); }
        Ok(src.len() as usize)
    }
    
    fn flush(&mut self) -> Result<(), ::std::io::Error> {
        Ok(())
    }
}

Реализации типажей глобальны.

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

O
Replying to #5

Две разные структуры тоже вполне себе решение :slight_smile:

C
Replying to #6

тоже вполне себе

да я как то старался избегать такого решения. хотя и очевидно, что по сути Channel и AdvChannel одного типа и функционально индетичны. Можно было бы в этом месте компилятору Rust и “сахарком посыпать” чтобы избегать всяких self.0.

Реализации типажей глобальны. Если бы модуля b не было, то ты бы мог импортировать в другой модуль сам типаж и саму структуру и сразу использовать факт реализации одного для другого:

mod c {
   use Bar;
   use std::io::Read;

    let mut b = Bar(0);
    b.read(.....);
}

И тут не важно где сама реализация была.

ну а так бы я импортировал модуль a или b - только ту имплементацию которая в данном месте мне нужна.

Если бы она была, то или я не понимаю как бы система работала

все документируется.

Типа - есть вот такая имплементация… а ещё вот такая, и такая для того чтобы выбрать импортируйте модуль а или b или с

легко, НЕ преписывая кода, только переключая импортируемые модули меняем функционал

в других языках это активно используется и выставляется как фича…

O

А вариант с двумя типажами, который я выше написал, чем тебе не нравится? Именно как ты пишешь - импортируешь тот типаж, который нужен в нужное место и пользуешься его функционалом. Захотели изменить поведение - заменили use одного типажа на другой.

C
Replying to #8

я изучаю этот пример.
внимательно перечитываю Trait Objects и Trait Object Layout

обязательно отвечу. на первый взгляд всё работоспособно.
Ещё раз спасибо за подсказки.
Очень ценню!

C
Replying to #9

не, что то не лезет сова на глобус.

struct Channel(u8);

impl ::std::io::Read for Channel {
    fn read(&mut self, dst: &mut [u8]) -> Result<usize, ::std::io::Error> { Ok(0) }
}

impl ::std::io::Write for Channel {
    fn write(&mut self, src: &[u8]) -> Result<usize, ::std::io::Error> { Ok(0) }
    
    fn flush(&mut self) -> Result<(), ::std::io::Error> { Ok(()) }
}

mod adv {
    trait AdvWrite: ::std::io::Write {
        fn write(&mut self, src: &[u8]) -> Result<usize, ::std::io::Error>;
        fn flush(&mut self) -> Result<(), ::std::io::Error>;
    }
    
    impl AdvWrite for super::Channel {
        fn write(&mut self, src: &[u8]) -> Result<usize, ::std::io::Error> { Ok(2) }
        
        fn flush(&mut self) -> Result<(), ::std::io::Error> {
            Ok(())
        }
    }
    
    trait AdvRead: ::std::io::Read { fn read(&mut self, dst: &mut [u8]) -> Result<usize, ::std::io::Error>; }
    
    impl AdvRead for super::Channel {
        fn read(&mut self, dst: &mut [u8]) -> Result<usize, ::std::io::Error> { Ok(2) }
    }
}


static mut PLAIN: Channel = Channel(0);
static mut ADVANCED: Channel = { use adv; Channel(0) };


fn main()
{
    let data:[u8;0]=[];
    println!("{}" ,PLAIN.write(&data)); // <<<< ????
}
O
Replying to #10

Поправил сову:

use std::io;

struct Channel(u8);

impl io::Read for Channel {
    fn read(&mut self, _dst: &mut [u8]) -> io::Result<usize> {
        Ok(0)
    }
}

impl io::Write for Channel {
    fn write(&mut self, _src: &[u8]) -> io::Result<usize> {
        Ok(0)
    }

    fn flush(&mut self) -> io::Result<()> {
        Ok(())
    }
}

mod adv {
    use std::io;

    pub trait AdvWrite: io::Write {
        fn write(&mut self, src: &[u8]) -> io::Result<usize>;
        fn flush(&mut self) -> io::Result<()>;
    }

    impl AdvWrite for ::Channel {
        fn write(&mut self, _src: &[u8]) -> io::Result<usize> {
            Ok(2)
        }

        fn flush(&mut self) -> io::Result<()> {
            Ok(())
        }
    }

    trait AdvRead: io::Read {
        fn read(&mut self, _dst: &mut [u8]) -> io::Result<usize>;
    }

    impl AdvRead for ::Channel {
        fn read(&mut self, _dst: &mut [u8]) -> io::Result<usize> {
            Ok(2)
        }
    }
}

static mut CHANNEL: Channel = Channel(0);

fn main() {
    let data: [u8; 0] = [];
    {
        use io::Write;

        unsafe {
            println!("{:?}", CHANNEL.write(&data)); // uses `impl io::Write for Channel`
        }
    }
    {
        use adv::AdvWrite;

        unsafe {
            println!("{:?}", CHANNEL.write(&data)); // uses `impl AdvRead for ::Channel`
        }
    }
}

Playground

use типажа надо делать в месте использования переменной, а не ее объявления (т.е. можно для одного и того же объекта вызывать разные методы в разных местах).

Для работы с не обернутыми ничем изменямыми статиками нужен unsafe.

C
Replying to #11

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

V

Любопытно, что если завернуть impl Readы в функции, т.е. скрыть из модульного неймспейса, ошибка остается. Проверка осуществляется на уровне анализа исходного кода, а не во время компиляции?

O
Replying to #13

Попробовал завернуть в функции и, вроде, все так же. Можешь показать что именно ты делал?

V
Replying to #14

Скажите мне, что мне показалось. Как это компилится?
https://play.rust-lang.org/?gist=1c6801398567856be45ba3e522912c08&version=stable&mode=debug&edition=2015

O
Replying to #15

А чего бы примеру не работать?

V
Replying to #16

Да-да, Пастернака я читал, но всё равно выглядит каким-то читом, что ли.

O
Replying to #17

Были какие-то RFC про приватные реализации типажей, видимо вы с @cheblin этого хотите, но сейчас сходу ссылок найти не могу.

V
Replying to #18

Я хочу понять, не зачем, а как это сделано.

O
Replying to #19

Как? Думаю что откуда-то отсюда надо тогда начинать в исходники закапываться:

https://rust-lang-nursery.github.io/rustc-guide/traits/index.html