You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
What you'll learn: Lessons from a real-world translation of ~100K lines of C++ to ~90K lines of Rust across ~20 crates. Five key transformation patterns and the architectural decisions behind them.
We translated a large C++ diagnostic system (~100K lines of C++) into a Rust implementation (~20 Rust crates, ~90K lines)
This section shows the actual patterns used — not toy examples, but real production code
The five key transformations:
#
C++ Pattern
Rust Pattern
Impact
1
Class hierarchy + dynamic_cast
Enum dispatch + match
~400 → 0 dynamic_casts
2
shared_ptr / enable_shared_from_this tree
Arena + index linkage
No reference cycles
3
Framework* raw pointer in every module
DiagContext<'a> with lifetime borrowing
Compile-time validity
4
God object
Composable state structs
Testable, modular
5
vector<unique_ptr<Base>> everywhere
Trait objects only where needed (~25 uses)
Static dispatch default
Before and After Metrics
Metric
C++ (Original)
Rust (Rewrite)
dynamic_cast / type downcasts
~400
0
virtual / override methods
~900
~25 (Box<dyn Trait>)
Raw new allocations
~200
0 (all owned types)
shared_ptr / reference counting
~10 (topology lib)
0 (Arc only at FFI boundary)
enum class definitions
~60
~190 pub enum
Pattern matching expressions
N/A
~750 match
God objects (>5K lines)
2
0
Case Study 1: Inheritance hierarchy → Enum dispatch
The C++ Pattern: Event Class Hierarchy
// C++ original: Every GPU event type is a class inheriting from GpuEventBaseclassGpuEventBase {
public:virtual~GpuEventBase() = default;
virtualvoidProcess(DiagFramework* fw) = 0;
uint16_t m_recordId;
uint8_t m_sensorType;
// ... common fields
};
classGpuPcieDegradeEvent : publicGpuEventBase {
public:voidProcess(DiagFramework* fw) override;
uint8_t m_linkSpeed;
uint8_t m_linkWidth;
};
classGpuPcieFatalEvent : publicGpuEventBase { /* ... */ };
classGpuBootEvent : publicGpuEventBase { /* ... */ };
// ... 10+ event classes inheriting from GpuEventBase// Processing requires dynamic_cast:voidProcessEvents(std::vector<std::unique_ptr<GpuEventBase>>& events,
DiagFramework* fw) {
for (auto& event : events) {
if (auto* degrade = dynamic_cast<GpuPcieDegradeEvent*>(event.get())) {
// handle degrade...
} elseif (auto* fatal = dynamic_cast<GpuPcieFatalEvent*>(event.get())) {
// handle fatal...
}
// ... 10 more branches
}
}
The Rust Solution: Enum Dispatch
// Example: types.rs — No inheritance, no vtable, no dynamic_cast#[derive(Debug,Clone,PartialEq,Eq,Serialize,Deserialize)]pubenumGpuEventKind{PcieDegrade,PcieFatal,PcieUncorr,Boot,BaseboardState,EccError,OverTemp,PowerRail,ErotStatus,Unknown,}
// Example: manager.rs — Separate typed Vecs, no downcasting neededpubstructGpuEventManager{sku:SkuVariant,degrade_events:Vec<GpuPcieDegradeEvent>,// Concrete type, not Box<dyn>fatal_events:Vec<GpuPcieFatalEvent>,uncorr_events:Vec<GpuPcieUncorrEvent>,boot_events:Vec<GpuBootEvent>,baseboard_events:Vec<GpuBaseboardEvent>,ecc_events:Vec<GpuEccEvent>,// ... each event type gets its own Vec}// Accessors return typed slices — zero ambiguityimplGpuEventManager{pubfndegrade_events(&self) -> &[GpuPcieDegradeEvent]{&self.degrade_events}pubfnfatal_events(&self) -> &[GpuPcieFatalEvent]{&self.fatal_events}}
Why Not Vec<Box<dyn GpuEvent>>?
The Wrong Approach (literal translation): Put all events in one heterogeneous collection, then downcast — this is what C++ does with vector<unique_ptr<Base>>
The Right Approach: Separate typed Vecs eliminate all downcasting. Each consumer asks for exactly the event type it needs
Performance: Separate Vecs give better cache locality (all degrade events are contiguous in memory)
Case Study 2: shared_ptr tree → Arena/index pattern
The C++ Pattern: Reference-Counted Tree
// C++ topology library: PcieDevice uses enable_shared_from_this // because parent and child nodes both need to reference each otherclassPcieDevice : publicstd::enable_shared_from_this<PcieDevice> {
public:
std::shared_ptr<PcieDevice> m_upstream;
std::vector<std::shared_ptr<PcieDevice>> m_downstream;
// ... device datavoidAddChild(std::shared_ptr<PcieDevice> child) {
child->m_upstream = shared_from_this(); // Parent ↔ child cycle!
m_downstream.push_back(child);
}
};
// Problem: parent→child and child→parent create reference cycles// Need weak_ptr to break cycles, but easy to forget
The Rust Solution: Arena with Index Linkage
// Example: components.rs — Flat Vec owns all devicespubstructPcieDevice{pubbase:PcieDeviceBase,pubkind:PcieDeviceKind,// Tree linkage via indices — no reference counting, no cyclespubupstream_idx:Option<usize>,// Index into the arena Vecpubdownstream_idxs:Vec<usize>,// Indices into the arena Vec}// The "arena" is simply a Vec<PcieDevice> owned by the tree:pubstructDeviceTree{devices:Vec<PcieDevice>,// Flat ownership — one Vec owns everything}implDeviceTree{pubfnparent(&self,device_idx:usize) -> Option<&PcieDevice>{self.devices[device_idx].upstream_idx.map(|idx| &self.devices[idx])}pubfnchildren(&self,device_idx:usize) -> Vec<&PcieDevice>{self.devices[device_idx].downstream_idxs.iter().map(|&idx| &self.devices[idx]).collect()}}
Key Insight
No shared_ptr, no weak_ptr, no enable_shared_from_this
No reference cycles possible — indices are just usize values
Better cache performance — all devices in contiguous memory
Simpler reasoning — one owner (the Vec), many viewers (indices)
graph LR
subgraph "C++ shared_ptr Tree"
A1["shared_ptr<Device>"] -->|"shared_ptr"| B1["shared_ptr<Device>"]
B1 -->|"shared_ptr (parent)"| A1
A1 -->|"shared_ptr"| C1["shared_ptr<Device>"]
C1 -->|"shared_ptr (parent)"| A1
style A1 fill:#ff6b6b,color:#000
style B1 fill:#ffa07a,color:#000
style C1 fill:#ffa07a,color:#000
end
subgraph "Rust Arena + Index"
V["Vec<PcieDevice>"]
V --> D0["[0] Root<br/>upstream: None<br/>down: [1,2]"]
V --> D1["[1] Child<br/>upstream: Some(0)<br/>down: []"]
V --> D2["[2] Child<br/>upstream: Some(0)<br/>down: []"]
style V fill:#51cf66,color:#000
style D0 fill:#91e5a3,color:#000
style D1 fill:#91e5a3,color:#000
style D2 fill:#91e5a3,color:#000
end