Search code examples
rusttraitssharingreference-counting

Sharing objects implementing trait in Rust


I have a trait for objects that can provide bytes from some index. These could be files, processes being ptraced, caches over other byte providers, etc.:

use std::result::Result;
use std::io::Error;

trait ByteProvider {
    fn provide_bytes(&mut self, index: usize, dest: &mut[u8]) -> Result<usize, Error>;
}

struct ZeroProvider { }

impl ByteProvider for ZeroProvider {
    fn provide_bytes(&mut self, _index: usize, dest: &mut[u8]) -> Result<usize, Error> {
        dest.iter_mut().for_each(|e| *e = 0);
        Ok(dest.len())
    }
}

I also have a set of widgets working on any generic type implementing the ByteProvider trait:

struct HexDump<T: ByteProvider> {
    provider: T
}

struct Disassembler<T: ByteProvider> {
    provider: T
}

impl<T: ByteProvider> HexDump<T> {
    pub fn new(provider: T) -> Self { Self { provider } }
    pub fn dump(&mut self) {
        let mut bytes = [0; 16];
        self.provider.provide_bytes(0, &mut bytes).unwrap();
        println!("{}", bytes.iter().map(|e| format!("{:02x}", e)).collect::<Vec<String>>().join(" "));
    }
}

impl<T: ByteProvider> Disassembler<T> {
    pub fn new(provider: T) -> Self { Self { provider } }
    pub fn disassemble(&mut self) {
        println!("Disassembly");
    }
}

This works fine:

fn main() {
    let provider = ZeroProvider {};
    let mut dumper = HexDump::new(provider);
    dumper.dump();
}

...however, I would like multiple views into the same data:

fn main() {
    let provider = ZeroProvider {};
    let mut dumper = HexDump::new(provider);
    let mut disassembler = Disassembler::new(provider);
    dumper.dump();
    disassembler.disassemble();
}

However this is of course not valid Rust, so I turned to reference counted smart pointers for help expecting this to work:

use std::rc::Rc;
fn main() {
    let provider = Rc::new(ZeroProvider {});
    let mut dumper = HexDump::new(Rc::clone(&provider));
    let mut disassembler = Disassembler::new(Rc::clone(&provider));
    dumper.dump();
    disassembler.disassemble();
}

However, the compiler does not like this:

27  | impl<T: ByteProvider> HexDump<T> {
    |         ^^^^^^^^^^^^  ----------
    |         |
    |         unsatisfied trait bound introduced here
...
error[E0599]: the method `dump` exists for struct `HexDump<Rc<_, _>>`, but its trait bounds were not satisfied
   --> src/main.rs:48:12
    |
19  |   struct HexDump<T: ByteProvider> {
    |   ------------------------------- method `dump` not found for this struct
...
48  |       dumper.dump();
    |              ^^^^ method cannot be called on `HexDump<Rc<_, _>>` due to unsatisfied trait bounds

...with similar errors for Disassembler and I cannot decipher that. What am I missing?

I think the problem is that Rc does not implement my trait but doesn't it expose its nested objects interface?

I would like for the widgets (HexDump and Disassembler) not to be aware that their byte providers are being shared if possible. Can this be accomplished?


Solution

  • As stated in the comments, the intended usage requires the ByteProvider to be shared between the multiples widgets which are in use at a given time.
    This implies that the .provide_bytes() function should use a shared reference (&self) instead of an exclusive reference (&mut self); I discovered here that the shared/exclusive aspect should be considered first in the design, then the constant/mutable aspect is only a consequence of this choice.
    Then, each widget can keep a shared reference to this ByteProvider (instead of owning it).
    Of course, the ByteProvider must outlive the widgets; if we cannot guaranty that statically, then Rc<T> should be used instead of &T.

    With the ZeroProvider example, there is no problem since it has no data members, then nothing to be mutated.
    On the other hand, if we have a provider with an internal state which needs to be mutated when used, the &self would be problematic.
    That's here that we need interior mutability: the idea is to check the exclusive access at runtime instead of compile time.
    As shown on the CountProvider example below, the internal state (a simple integer here for brevity) is embedded in a RefCell in order to mutably borrow this internal state only when needed (the equivalent in a multithreaded context would be RwLock).

    trait ByteProvider {
        fn provide_bytes(
            &self,
            index: usize,
            dest: &mut [u8],
        ) -> Result<usize, Box<dyn std::error::Error>>;
    }
    
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    struct ZeroProvider {}
    impl ByteProvider for ZeroProvider {
        fn provide_bytes(
            &self,
            _index: usize,
            dest: &mut [u8],
        ) -> Result<usize, Box<dyn std::error::Error>> {
            dest.iter_mut().for_each(|e| *e = 0);
            Ok(dest.len())
        }
    }
    
    struct CountProvider {
        count: std::cell::RefCell<u8>,
    }
    impl CountProvider {
        fn new() -> Self {
            Self {
                count: std::cell::RefCell::new(0),
            }
        }
    }
    impl ByteProvider for CountProvider {
        fn provide_bytes(
            &self,
            _index: usize,
            dest: &mut [u8],
        ) -> Result<usize, Box<dyn std::error::Error>> {
            let mut count = self.count.borrow_mut();
            dest.iter_mut().for_each(|e| {
                *e = *count;
                *count += 1;
            });
            Ok(dest.len())
        }
    }
    
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    struct HexDump<'a, T> {
        provider: &'a T,
    }
    impl<'a, T: ByteProvider> HexDump<'a, T> {
        pub fn new(provider: &'a T) -> Self {
            Self { provider }
        }
        pub fn dump(&self) {
            let mut bytes = [0; 16];
            self.provider.provide_bytes(0, &mut bytes).unwrap();
            println!(
                "Dumping {}",
                bytes
                    .iter()
                    .map(|e| format!("{:02x}", e))
                    .collect::<Vec<String>>()
                    .join(" ")
            );
        }
    }
    
    struct Disassembler<'a, T> {
        provider: &'a T,
    }
    impl<'a, T: ByteProvider> Disassembler<'a, T> {
        pub fn new(provider: &'a T) -> Self {
            Self { provider }
        }
        pub fn disassemble(&self) {
            let mut bytes = [0; 16];
            self.provider.provide_bytes(0, &mut bytes).unwrap();
            println!("Disassembling {:?}", bytes);
        }
    }
    
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    fn main() {
        let provider = ZeroProvider {};
        let dumper = HexDump::new(&provider);
        let disassembler = Disassembler::new(&provider);
        dumper.dump();
        disassembler.disassemble();
        //
        let provider = CountProvider::new();
        let dumper = HexDump::new(&provider);
        let disassembler = Disassembler::new(&provider);
        dumper.dump();
        disassembler.disassemble();
    }
    /*
    Dumping 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    Disassembling [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    Dumping 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
    Disassembling [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]
    */