Let’s understand what is Blanket Implementation in Rust
In this post, i will try to explain what blanket implementation means in Rust While writing the RTC HAL chapter for the Rust Embedded Drivers (RED) book, I needed to use blanket implementations as a design choice. Instead of explaining this concept in the book and cluttering the book, I thought it would be better to write a blog post instead. That’s how i end up with my first blog post in implRust :) The name comes from the English idiom where “blanket” means something that covers or applies to multiple things at once. A blanket implementation lets you implement a trait for all types that meet certain conditions. Instead of writing the same trait implementation for different types one by one, you write it once and it applies to all matching types. Here’s the basic syntax: You can read it as “For any type T that meets SomeCondition, implement MyTrait for T” Let’s say we create a trait called Now any type that implements Debug automatically gets the Loggable trait. We didn’t have to write separate implementations for Vec Blanket implementations are used extensively in the Rust standard library. A great example is how the ToString trait is implemented for any type that implements the Display trait: This means you don’t need to manually implement ToString for your types. If your type implements Display, it automatically gets the to_string() method for free. You can read more about this in the Rust book. Here’s a simplified version of how embedded-hal uses blanket implementations for I2C. It implements the I2c trait for mutable references (&mut T) to all types that already implement I2c: This allows drivers to be flexible - they can take either ownership of the I2C device or just borrow it. Without this blanket implementation, you would need separate functions or the driver would be forced to choose between taking ownership or taking a reference, limiting how it can be used. Let’s comment out the blanket implementation and see what happens: Without the blanket implementation, you need two separate functions - one for owned values and one for references. Your generic functions can’t work with both, so users have to pick the right function based on how they’re using their data. Blanket implementations are powerful, but they come with an important limitation: once you create a blanket implementation, you cannot create specific implementations for individual types that meet the same condition. For example, let’s say we want to add a Human struct that also implements Debug, but we want it to log differently than other types. We can’t do this: You’ll get a “conflicting implementations of trait” error because Rust can’t decide which implementation to use for Human - the blanket one or the specific one. This is why you need to carefully consider whether a blanket implementation is the right choice for your use case. What is a Blanket Implementation?
Example
Loggable
with a log method. Instead of implementing this trait for each individual type, we can use a blanket implementation for all types that implement the Debug trait:use Debug;
// Blanket implementation
Rust Standard Library
Example Use Case: Embedded HAL’s I2C Blanket Implementation
use I2c;
Why is this useful?
use I2c;
// Function that takes ownership
// Function that takes a mutable reference
With Great Power Comes Great Responsibility
use Debug;
// Blanket implementation