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?
As stated in the comments, the intended usage requires the
ByteProviderto 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
ByteProvidermust outlive the widgets; if we cannot guaranty that statically, thenRc<T>should be used instead of&T.With the
ZeroProviderexample, 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
&selfwould 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
CountProviderexample below, the internal state (a simple integer here for brevity) is embedded in aRefCellin order to mutably borrow this internal state only when needed (the equivalent in a multithreaded context would beRwLock).