Foreword: I asked this (or a very similar question) on one of the Rust discord servers a few days ago, and after some discussion I ended up going with static dispatch via an enum that also implements T
. This works well. Many thanks to the people over there for their assistance. But I also never figured out how to solve my original problem of how to box T
correctly when T has associated types that are also traits. I'd like to know how to solve this to further my understanding of Rust. I'm asking again here instead of Discord so I can post a long-form question with a bit more background.
I'm working on a tool that communicates with various i2c devices (temp sensors, etc). The i2c devices are connected to USB->i2c adapters. Many such adapters exist, such as the FT232H and MCP2221. Crates exist for these adapters that work within the embedded_hal
ecosystem, such as ftdi_embedded_hal
and mcp2221
for the aforementioned adapters, respectively.
I'd like to support more than one adapter type and multiple of each at a time. Which adapters to use and what i2c devices are connected to them are defined in a config file that's loaded at startup.
It's worth nothing that even though I mention i2c and embedded_hal
, this tool targets standard Linux (either x86_64 or aarch64) and has full access to the standard library.
Each adapter crate provides an embedded_hal::i2c::I2c
implementation. As I'd like to support multiple adapter types, I have to support multiple different implementations of I2c
. The I2c
trait is also used by all the downstream i2c device driver structs as a generic parameter (for example, see the BME280 driver)
I want to put all the adapter I2c
implementations into a HashMap.
let i2c_busses: HashMap<String, Box<dyn I2c<???>>> = HashMap::new()
However, I'm stuck trying to Box I2c
...
I2c
is defined in embedded_hal
(v1.0) as:
pub trait I2c<A: AddressMode = SevenBitAddress>: ErrorType {
// trait fns
}
pub trait ErrorType {
type Error: Error;
}
pub trait Error: core::fmt::Debug { .. }
As the associated type Error
is a trait, each adapter crate brings along its own implementation. I think I need this to be dyn
too?
I'm not sure what type signature I should use for Box.
Box<dyn eh1::i2c::I2c>
doesn't work:| let b: Box<dyn eh1::i2c::I2c> = Box::new(init_ft232h()?); | ^^^^^^^^^^^^^ help: specify the associated type: `eh1::i2c::I2c<Error = Type>`
Box<dyn eh1::i2c::I2c<Error = dyn eh1::i2c::Error>>
doesn't work:| let b: Box<dyn eh1::i2c::I2c<Error = dyn eh1::i2c::Error>> = Box::new(init_ft232h()?); | ^^^^^^^^^^^^^^^^^^^^^^^^ expected `dyn Error`, found `Error<TimeoutError>` | = note: expected trait object `(dyn embedded_hal::i2c::Error + 'static)` found enum `ftdi_embedded_hal::Error<TimeoutError>` = note: required for the cast from `Box<ftdi_embedded_hal::I2c<Ft232h>>` to `Box<dyn embedded_hal::i2c::I2c<Error = (dyn embedded_hal::i2c::Error + 'static)>>`
(signature for new_ft232h
is: fn init_ft232h() -> Result<ftdi_embedded_hal::I2c<libftd2xx::Ft232h>>
)
I think that I need to somehow cast, Box, or wrap the error type. But I'm not sure how to do this.
I also need the Box to also impl the I2c
trait so that I can pass it into the downstream i2c driver structs. I think this is done with a new type wrapper that impls I2c
and delegates fns it to the Box.
Appreciate any help on this.