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

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

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

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

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

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

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

1 симпатия

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

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

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

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

мне сейчас не до изысканий, мне рабочую версию на 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

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

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

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

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

Затрудняюсь дать ссылку прям вот на конкретный ответ “почему нельзя дважды реализовать один и тот же типаж для одного и того же типа”, оно просто логически вытекает из того как работают типажи: https://doc.rust-lang.org/book/first-edition/traits.html

Ну и может само описание ошибки еще: https://doc.rust-lang.org/stable/error-index.html#E0119

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

Реализации типажей глобальны. Если бы модуля 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.

1 симпатия

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

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

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 для типа работает глобально, вне всяких границ модуля.

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

1 симпатия

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

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

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

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

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

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

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

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

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

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

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

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

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

1 симпатия

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

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

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

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)); // <<<< ????
}

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

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.

2 симпатии

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

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

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

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

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

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

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

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

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

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