Backend
Does Rust Support Inheritance? Yes, No, and Maybe, All in the Same File
Mariusz Jurgielewicz Dev.to (EN Zone)
1 views
Does Rust Support Inheritance? Yes, No, and Maybe, All in the Same File
C started with plain structs: bags of fields without attached behavior. C++ added methods and class inheritance on top, letting a Derived class reuse Base fields and virtual methods while overriding only what differed. For many developers, that transition from struct to class hierarchy defines what object orientation looks like.
Rust has structs and traits. Traits look enough like interfaces that developers frequently ask whether Rust supports inheritance the way C++ or Java does. While maintaining epdsi, a no_std driver crate for e-paper displays, I ran into concrete examples of yes, no, and maybe all within the same codebase.
What inheritance actually buys you
In Java, if Ssd1680Controller and Ssd1681Controller are two chips in the same family that share almost all of their init sequence and differ only in how they trigger a refresh, you would write them as a hierarchy:
abstract class Ssd168xController implements EpdController {
protected Ssd168xVariant variant;
public void initSequence(Bus bus, Delay delay) {
// shared register writes, common to both chips
}
public void triggerRefresh(Bus bus, Delay delay) {
// shared power envelope, branches on variant only where the chips disagree
}
}
class Ssd1680Controller extends Ssd168xController {}
class Ssd1681Controller extends Ssd168xController {}
The subclasses inherit every method directly. With extends, fields live once in the base class, and calls on an Ssd1680Controller instance dispatch through the vtable to the shared implementation unless overridden. You only specify the differences.
What the Rust code actually looks like
epdsi has two chip families structured similarly to that Java example: Ssd168xController covers the SSD1680 and SSD1681, while Jd7966xController covers the JD79660AA and JD79661AA. Both involve one chip family with two silicon variants sharing register logic. Here is what src/controllers/ssd168x.rs does:
pub struct Ssd168xController {
width: u32,
height: u32,
variant: Ssd168xVariant,
// ...
}
pub struct Ssd1680Controller {
inner: Ssd168xController,
}
pub struct Ssd1681Controller {
inner: Ssd168xController,
}
Without extends, Ssd1680Controller wraps Ssd168xController as an internal field. Each method required by the EpdController trait must be implemented manually on Ssd1680Controller, forwarding directly to inner:
async fn init_sequence<DELAY: DelayNs>(
&mut self,
bus: &mut SpiBusWrapper<SPI, DC, RST, BUSY>,
delay: &mut DELAY,
) -> Result<(), Self::Error> {
self.inner.init_sequence(bus, delay).await
}
EpdController defines six methods, so this forwarding boilerplate is written for Ssd1680Controller and repeated identically for Ssd1681Controller.
The variant-specific differences live inside Ssd168xController itself, handled through a match self.variant. The wrapper types exist primarily so callers get explicit constructors (Ssd1680Controller::new(...) and Ssd1681Controller::new(...)) rather than having to pass a variant argument that could be misconfigured.
src/controllers/jd7966x.rs follows the same structure for a different chip family, noting in its doc comments that it mirrors the relationship modeled in Ssd168xController. Because the language provides no built-in syntax to abstract this delegation away, the pattern is duplicated by hand.
What Rust has instead
Rust provides several mechanisms for code reuse, but none allow one struct to inherit fields and methods from another:
Traits with default methods resemble default methods in Java interfaces. A trait can provide a default implementation that implementors can keep or override. In epdsi, all EpdController methods are mandatory, but default methods are common across the wider ecosystem.
Supertraits allow one trait to require another (trait Wait: InputPin), functioning like interface inheritance to compose contracts rather than state.
Composition means embedding one struct inside another, as Ssd1680Controller does with Ssd168xController. While Java developers have long advocated preferring composition over inheritance, Rust leaves no other choice when sharing state.
epdsi avoids using Deref polymorphism here. Implementing Deref on a wrapper type allows method calls to dereference to an inner struct, avoiding manual forwarding for inherent methods. However, Deref does not satisfy trait bounds, so the six EpdController trait methods still require explicit implementations. Explicit delegation keeps trait requirements clear rather than obscuring them behind deref coercion.
The fundamental constraint is where state lives. An abstract class in Java holds fields and passes them down to subclasses. Rust traits cannot hold state. When multiple types share fields (such as width, height, and variant), that data must live in a concrete struct, which leads directly to composition.
Yes, no, maybe
No, if inheritance means class inheritance via extends: automatically inheriting fields and methods with selective overrides. Rust structs cannot extend other structs.
Yes, if referring to interface inheritance: traits can require supertraits, provide default method implementations, and allow implementors to selectively override them.
Maybe, if referring to sharing implementation across multiple distinct types. As seen with Ssd1680Controller and Ssd1681Controller, Rust requires explicit forwarding delegation. The compiler does not generate forwarding shims automatically, so consistency between wrappers relies on careful manual wiring.
This design deliberately separates concerns that C++ unified. C++ combined data layout and behavior inheritance into a single mechanism, which works well until it encounters edge cases like the diamond problem. Rust separates them completely: structs manage data, traits define contracts, and they never collapse into a class hierarchy. While ssd168x.rs and jd7966x.rs might superficially resemble base and derived classes, they are structs wrapping another struct with manual delegation. That verbosity is the explicit trade-off Rust makes to avoid the pitfalls of traditional class hierarchies.
epdsi is available on crates.io and GitHub, a no_std, embedded-hal 1.0 driver framework for e-paper displays covering SSD1680/1681/1677, UC8253, JD79660/79661, ED2208, and Pervasive Displays COGs.
Read original: https://dev.to/melastmohican/does-rust-support-inheritance-yes-no-and-maybe-all-in-the-same-file-3fi1
← Previous
Your LLM cost estimate is wrong above 200,000 tokens
Next →
Cross-Chain Bridge Risk Assessment: Robinhood
Related
The Matrix of HFT: Unpacking the Hype
Backend
0
Dev.to (EN Zone)
Construindo um Pipeline de Processamento de Pedidos com o Padrão Chain of Responsibility em Java
Backend
0
Dev.to (EN Zone)
Built a self-hosted server control panel with Laravel + Livewire (broker/privilege-separation architecture)
Backend
3
Reddit r/php
Context aware Spring AI chat microservice
Backend
4
DEV Community
Comments0
No comments yet — be the first