< Return to Blog

Sometimes I do silly things, even when the Rust compiler is trying to help (Impl Traits)...

Today I spent a bit of time scratching my head as I've been integrating a crate I wrote a long-time ago on ESP32 bare-metal rust into a new project with the Embassy-stm32 HAL (for ARM, in my case the STM32H747XI chip).
Here's the offending code:
impl<'a, I2C> RTClock<'a, I2C> where I2C: embedded_hal_1::i2c::I2c<Error = BoardError>, { #[allow(dead_code)] pub fn new(bus_manager: BusManagerCortexM<I2cDriver<'a>>) -> Result<Self, BoardError> { Ok(Self { datetime: None, bus_manager, phantom: PhantomData, }) } // snip... }
Bear with me as the error takes a bit of reading, but we'll break it down
error[E0599]: the function or associated item `new` exists for struct `RTClock<'_, I2c<'_, I2C2>>`, but its trait bounds were not satisfied --> src/board/tasks.rs:132:63 | 132 | let rtc_clock = RTClock::<embassy_stm32::i2c::I2c<I2C2>>::new(bus_manager) | ^^^ function or associated item cannot be called on `RTClock<'_, I2c<'_, I2C2>>` due to unsatisfied trait bounds | ::: /Users/mdesilva/.cargo/git/checkouts/embassy-9312dcb0ed774b29/4c7ed5e/embassy-stm32/src/i2c/mod.rs:74:1 | 74 | pub struct I2c<'d, T: Instance, TXDMA = NoDma, RXDMA = NoDma> { | ------------------------------------------------------------- doesn't satisfy `<_ as ErrorType>::Error = BoardError` | ::: src/board/rtc.rs:20:1 | 20 | pub struct RTClock<'a, I2C> { | --------------------------- function or associated item `new` not found for this struct | note: if you're trying to build a new `RTClock<'_, embassy_stm32::i2c::I2c<'_, I2C2>>`, consider using `RTClock::<'a, I2C>::new` which returns `Result<RTClock<'_, _>, BoardError>` --> src/board/rtc.rs:31:5 | 31 | pub fn new(bus_manager: BusManagerCortexM<I2cDriver<'a>>) -> Result<Self, BoardError> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: trait bound `<embassy_stm32::i2c::I2c<'_, I2C2> as embedded_hal::i2c::ErrorType>::Error = BoardError` was not satisfied --> src/board/rtc.rs:28:35 | 26 | impl<'a, I2C> RTClock<'a, I2C> | ---------------- 27 | where 28 | I2C: embedded_hal_1::i2c::I2c<Error = BoardError>, | ^^^^^^^^^^^^^^^^^^ unsatisfied trait bound introduced here = help: items from traits can only be used if the trait is implemented and in scope = note: the following trait defines an item `new`, perhaps you need to implement it: candidate #1: `Frame`
Let's try and figure out what it's trying to say. My calling code is let rtc_clock = RTClock::<embassy_stm32::i2c::I2c<I2C2>>::new(bus_manager) and it seems like we've asked for a specific Error type, in this case a where clause with I2C: embedded_hal_1::i2c::I2c<Error = BoardError> -
function or associated item cannot be called on `RTClock<'_, I2c<'_, I2C2>>` due to unsatisfied trait bounds | ::: /Users/mdesilva/.cargo/git/checkouts/embassy-9312dcb0ed774b29/4c7ed5e/embassy-stm32/src/i2c/mod.rs:74:1 | 74 | pub struct I2c<'d, T: Instance, TXDMA = NoDma, RXDMA = NoDma> { | ------------------------------------------------------------- doesn't satisfy `<_ as ErrorType>::Error = BoardError`
I admit, I was tired looking at this for so long, so I didn't pay too much attention to the big clue, and focussed on the wrong part of the error log
31 | pub fn new(bus_manager: BusManagerCortexM<I2cDriver<'a>>) -> Result<Self, BoardError> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: trait bound `<embassy_stm32::i2c::I2c<'_, I2C2> as embedded_hal::i2c::ErrorType>::Error = BoardError` was not satisfied
From this point one wards it just feels like garbage, hence my circling around doing silly things.  I was changing parts of my code that had nothing to do with the underlying issue - pat your self on the back if you figured it out, and do share in the comments!!
--> src/board/rtc.rs:28:35 | 26 | impl<'a, I2C> RTClock<'a, I2C> | ---------------- 27 | where 28 | I2C: embedded_hal_1::i2c::I2c<Error = BoardError>, | ^^^^^^^^^^^^^^^^^^ unsatisfied trait bound introduced here = help: items from traits can only be used if the trait is implemented and in scope = note: the following trait defines an item `new`, perhaps you need to implement it: candidate #1: `Frame`
So, what did I miss? Well, it was staring at me all along:
::: src/board/rtc.rs:20:1 | 20 | pub struct RTClock<'a, I2C> { | --------------------------- function or associated item `new` not found for this struct
 This is really the issue, and now I know that whenever I see an err that talks about `Frame`, it's really trying to say, "Hey, I think the impl block you wrote, doesn't have the right signature, you expect it to have!"
The solution was to remove the where clause (commented out below):
impl<'a, I2C> RTClock<'a, I2C> // NOTE: Totally not needed! // where // I2C: embedded_hal_1::i2c::I2c<Error = BoardError>, { #[allow(dead_code)] pub fn new(bus_manager: BusManagerCortexM<I2cDriver<'a>>) -> Result<Self, BoardError> { // snip... } // snip... }
Removing the where clause and it compiles just fine.  Any suggestions on how to spot incorrect impl blocks, or ones that are incorrectly constrained?

Update

Reddit user CAD1997 suggested not to use new for impls due to the Frame trait ultimately causing some confusion
Yeah, the "function ... exists ... but its trait bounds weren't satisfied" error can be a bit unusefully noisy when the problem isn't actually unsatisfied bounds but instead overly restrictive bounds. The note about the Frame trait is also a reason why I generally think it's preferable to avoid trait method names like new.
I do love that the error basically ends up saying "RTClock::new doesn't exist; try using RTClock::new", though. Maybe the compiler could use that existing ::new heuristic to improve the diagnostic? It's probably almost never correct to suggest implementing a trait providing ::new.
Reddit user CAD1997
Of course, I had to try this out and sure enough the diagnostic information is a wee bit shorter, but we still need to dig through to find the root cause; I plan to reach out to the Rust team to see if this could be better improved.  It would be nice if we could have warnings for over-constrained impl traits may be?
error[E0599]: the function or associated item `get_clock` exists for struct `RTClock<'_, I2c<'_, I2C2>>`, but its trait bounds were not satisfied --> src/board/tasks.rs:132:63 | 132 | let rtc_clock = RTClock::<embassy_stm32::i2c::I2c<I2C2>>::get_clock(bus_manager) | ^^^^^^^^^ function or associated item cannot be called on `RTClock<'_, I2c<'_, I2C2>>` due to unsatisfied trait bounds | ::: /Users/mdesilva/.cargo/git/checkouts/embassy-9312dcb0ed774b29/4c7ed5e/embassy-stm32/src/i2c/mod.rs:74:1 | 74 | pub struct I2c<'d, T: Instance, TXDMA = NoDma, RXDMA = NoDma> { | ------------------------------------------------------------- doesn't satisfy `<_ as ErrorType>::Error = BoardError` | ::: src/board/rtc.rs:20:1 | 20 | pub struct RTClock<'a, I2C> { | --------------------------- function or associated item `get_clock` not found for this struct | note: if you're trying to build a new `RTClock<'_, embassy_stm32::i2c::I2c<'_, I2C2>>`, consider using `RTClock::<'a, I2C>::get_clock` which returns `Result<RTClock<'_, _>, BoardError>` --> src/board/rtc.rs:31:5 | 31 | pub fn get_clock(bus_manager: BusManagerCortexM<I2cDriver<'a>>) -> Result<Self, BoardError> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: trait bound `<embassy_stm32::i2c::I2c<'_, I2C2> as embedded_hal::i2c::ErrorType>::Error = BoardError` was not satisfied --> src/board/rtc.rs:28:35 | 26 | impl<'a, I2C> RTClock<'a, I2C> | ---------------- 27 | where 28 | I2C: embedded_hal_1::i2c::I2c<Error = BoardError>, | ^^^^^^^^^^^^^^^^^^ unsatisfied trait bound introduced here

Errata

I've been corrected on Reddit for a honest typo, whilst rushing through drafting this article. I've corrected my transgressions on asking people to uncover themselves!! Checkout the thread for some interesting feedback - thanks chaps!