Including two versions of a Rust crate in a single project
And how I was misled by a rustc compiler error
First, a little background…
Recently, I stumbled upon an obscure Rust development problem that I felt compelled to write about since I know others have or will be confused by it, as I was.
Currently, I’m working with several Rust Never Sleeps community members on a proof-of-concept (PoC) project writing a Raspberry Pi Pico WiFi driver in Rust. Since the Pico does not have WiFi hardware by default, we selected a daughterboard to work with from Pimoroni, the Pico Wireless Pack (which is really just a specialized ESP32-WROOM-32E module). Here’s a photo of a Pico and a WiFi board wired together on a breadboard.
I created an experimental branch of our PoC source tree attempting to use the latest version of a crate for a BME280 sensor module that’s used to sample temperature, pressure and humidity. This module recently went all-in on the embedded-hal-1.0.0-alpha.X series instead of the “more stable” 0.2.X series. So, I enabled the feature flag for rp-hal to use the newer embedded-hal version, and removed any references to the older 0.2.X series.
I reached a rustc compiler error that seemed straightforward, but I really struggled to understand and solve on my own (I’m still quite new to working with Rust at the time of writing):
Compiling rp2040-hal v0.4.0
error[E0308]: mismatched types
--> src/main.rs:1803:9
|
1803 | &MODE_0,
| ^^^^^^^ expected struct `embedded_hal::spi::Mode`, found struct `Mode`
|
= note: expected reference `&embedded_hal::spi::Mode`
found reference `&Mode`
= note: perhaps two different versions of crate `embedded_hal` are being used?
For more information about this error, try `rustc --explain E0308`.
error: could not compile `esp32-pico-wifi` due to previous error
And it’s very clear on what line this error originates, clearly from a method call to initialize a new rp2040_hal::Spi
struct instance:
let spi = spi.init(
&mut pac.RESETS,
clocks.peripheral_clock.freq(),
8_000_000u32.Hz(),
&MODE_0,
);
As I’ve come to trust rustc’s error messages and can usually interpret them literally, I went searching for where I might be importing two different versions of the embedded_hal crate. This is where my confusion first began - I was no longer explicitly importing two versions of embedded-hal, but I noticed from studying my Cargo.lock
file that the cortex-m crate was. And this is where I got very confused. I kept following different deadend paths trying to have my project only use the newer embedded-hal version. But no matter what I did, I struggled to resolve this problem.
Subscribe to the Relational Technologist newsletter so you don’t miss any future software engineering career advice, technical guides and training/workshop opportunities.
Better understanding of the problem
I decided that I needed some better background on the problem. So I joined the rp-hal Matrix chat server to get some help from the friendly community. They’ve been working with embedded-hal and Rust a lot longer than I have, so I was confident that they’d be able to help me learn better than my own experiments. And I was correct - they very quickly pointed me in the right direction to understanding the situation better, and provided a strong way of solving the issue.
The rustc compiler error I was experiencing was actually quite different than I expected. I actually need to use the two different versions of embedded-hal in my code since Rust considers data types to be different across crate major versions, even if these types share the exact same name. In my case, the type I’m working with to pass to Spi::init()
is:
&embedded_hal::spi::Mode
This type is exactly the same name in embedded-hal-0.2.X as it is in 1.0.0-alpha.X. But as I learned, they’re not the same types to rustc. rp-hal still expects the 0.2.X MODE_0
type to be passed into init()
and not the 1.0.0-alpha.X MODE_0
type.
A solution to the problem
So how did I solve this?
The answer is to import both major versions of embedded-hal and differentiate them by unique import names. This means that my Cargo.toml
file changed from:
embedded-hal = { version = "0.2.5", features=["unproven"] }
to:
embedded-hal-02 = { version = "0.2.7", package="embedded-hal" }
embedded-hal = { version = "=1.0.0-alpha.7" }
Now in my Rust code, I can differentiate between the two versions by the names I assigned above in Cargo.toml
:
use embedded_hal::digital::blocking::InputPin;
This makes use of InputPin
from the 1.0.0-alpha.X version, while:
use embedded_hal_02::spi::MODE_0;
makes use of MODE_0
from the 0.2.X series of embedded-hal.
Conclusion
Once I received some better background on how Rust operates in this situation, it became clear that it’s not a complex problem to understand and ultimately solve. But until I understood this, it was a confusing situation. I hope this article helps you understand better when you’ve fallen into this situation, what Rust is expecting and ultimately how to solve it.
Every language has obscure edge cases that need a bit of nuance to understand how to solve, and this is one of Rust’s. But this is where community of other Rust developers becomes very important to engage with. I hope this article helps the Rust community work more effectively with our beloved language.
As always, thanks for reading and passing this along to others who would enjoy reading the Relational Technologist.
Please enjoy!
Are you struggling to learn Rust?
I work with engineers just like you as a software engineer coach, and I’d love to help you master Rust, or something else.
I’m currently offering 1 free month* of 1:1 software engineer coaching, let me show you how we can grow your software engineer mastery more effectively together
Looking for a community to learn Rust in?
If a software engineer coach currently doesn’t sound like what you want for yourself, I also have an open source community dedicated to learning Rust, mastering Rust and doing both while building real open source software.
We offer weekly live meeting times and a Slack community to interact with other engineers any time, day or night.
It’s completely free and I welcome you to come check us out. We call ourselves Rust Never Sleeps.
To get involved further, simply fill out this short questionnaire.
*Offer good for May 2022