Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Inheritance vs Composition | 继承 vs 组合

What you’ll learn: Why Rust has no class inheritance, how traits + structs replace deep class hierarchies, and practical patterns for achieving polymorphism through composition.

你将学到什么: 为什么 Rust 没有类继承,trait + struct 如何取代深层类层次结构, 以及如何通过组合实现多态的实用模式。

Difficulty: Intermediate

难度: 中级

// C# - Class-based inheritance
public abstract class Animal
{
    public string Name { get; protected set; }
    public abstract void MakeSound();
    
    public virtual void Sleep()
    {
        Console.WriteLine($"{Name} is sleeping");
    }
}

public class Dog : Animal
{
    public Dog(string name) { Name = name; }
    
    public override void MakeSound()
    {
        Console.WriteLine("Woof!");
    }
    
    public void Fetch()
    {
        Console.WriteLine($"{Name} is fetching");
    }
}

// Interface-based contracts
public interface IFlyable
{
    void Fly();
}

public class Bird : Animal, IFlyable
{
    public Bird(string name) { Name = name; }
    
    public override void MakeSound()
    {
        Console.WriteLine("Tweet!");
    }
    
    public void Fly()
    {
        Console.WriteLine($"{Name} is flying");
    }
}

Rust Composition Model | Rust 的组合模型

#![allow(unused)]
fn main() {
// Rust - Composition over inheritance with traits
pub trait Animal {
    fn name(&self) -> &str;
    fn make_sound(&self);
    
    // Default implementation (like C# virtual methods)
    fn sleep(&self) {
        println!("{} is sleeping", self.name());
    }
}

pub trait Flyable {
    fn fly(&self);
}

// Separate data from behavior
#[derive(Debug)]
pub struct Dog {
    name: String,
}

#[derive(Debug)]
pub struct Bird {
    name: String,
    wingspan: f64,
}

// Implement behaviors for types
impl Animal for Dog {
    fn name(&self) -> &str {
        &self.name
    }
    
    fn make_sound(&self) {
        println!("Woof!");
    }
}

impl Dog {
    pub fn new(name: String) -> Self {
        Dog { name }
    }
    
    pub fn fetch(&self) {
        println!("{} is fetching", self.name);
    }
}

impl Animal for Bird {
    fn name(&self) -> &str {
        &self.name
    }
    
    fn make_sound(&self) {
        println!("Tweet!");
    }
}

impl Flyable for Bird {
    fn fly(&self) {
        println!("{} is flying with {:.1}m wingspan", self.name, self.wingspan);
    }
}

// Multiple trait bounds (like multiple interfaces)
fn make_flying_animal_sound<T>(animal: &T) 
where 
    T: Animal + Flyable,
{
    animal.make_sound();
    animal.fly();
}
}
graph TD
    subgraph "C# Inheritance Hierarchy"
        CS_ANIMAL["Animal (abstract class)"]
        CS_DOG["Dog : Animal"]
        CS_BIRD["Bird : Animal, IFlyable"]
        CS_VTABLE["Virtual method dispatch<br/>Runtime cost"]
        CS_COUPLING["[ERROR] Tight coupling<br/>[ERROR] Diamond problem<br/>[ERROR] Deep hierarchies"]
        
        CS_ANIMAL --> CS_DOG
        CS_ANIMAL --> CS_BIRD
        CS_DOG --> CS_VTABLE
        CS_BIRD --> CS_VTABLE
        CS_ANIMAL --> CS_COUPLING
    end
    
    subgraph "Rust Composition Model"
        RUST_ANIMAL["trait Animal"]
        RUST_FLYABLE["trait Flyable"]
        RUST_DOG["struct Dog"]
        RUST_BIRD["struct Bird"]
        RUST_IMPL1["impl Animal for Dog"]
        RUST_IMPL2["impl Animal for Bird"]
        RUST_IMPL3["impl Flyable for Bird"]
        RUST_STATIC["Static dispatch<br/>Zero cost"]
        RUST_FLEXIBLE["[OK] Flexible composition<br/>[OK] No hierarchy limits<br/>[OK] Mix and match traits"]
        
        RUST_DOG --> RUST_IMPL1
        RUST_BIRD --> RUST_IMPL2
        RUST_BIRD --> RUST_IMPL3
        RUST_IMPL1 --> RUST_ANIMAL
        RUST_IMPL2 --> RUST_ANIMAL
        RUST_IMPL3 --> RUST_FLYABLE
        RUST_IMPL1 --> RUST_STATIC
        RUST_IMPL2 --> RUST_STATIC
        RUST_IMPL3 --> RUST_STATIC
        RUST_ANIMAL --> RUST_FLEXIBLE
        RUST_FLYABLE --> RUST_FLEXIBLE
    end
    
    style CS_COUPLING fill:#ffcdd2,color:#000
    style RUST_FLEXIBLE fill:#c8e6c9,color:#000
    style CS_VTABLE fill:#fff3e0,color:#000
    style RUST_STATIC fill:#c8e6c9,color:#000
Rust 并不是“不能做面向对象”,而是把复用和多态从“继承层次”转成了“能力组合”。

Exercises | 练习

Exercise: Replace Inheritance with Traits | 练习:用 Trait 替代继承 (click to expand / 点击展开)

This C# code uses inheritance. Rewrite it in Rust using trait composition:

下面这段 C# 代码使用了继承。请用 Rust 的 trait 组合方式重写它:

public abstract class Shape { public abstract double Area(); }
public abstract class Shape3D : Shape { public abstract double Volume(); }
public class Cylinder : Shape3D
{
    public double Radius { get; }
    public double Height { get; }
    public Cylinder(double r, double h) { Radius = r; Height = h; }
    public override double Area() => 2.0 * Math.PI * Radius * (Radius + Height);
    public override double Volume() => Math.PI * Radius * Radius * Height;
}

Requirements:

  1. HasArea trait with fn area(&self) -> f64
  2. HasVolume trait with fn volume(&self) -> f64
  3. Cylinder struct implementing both
  4. A function fn print_shape_info(shape: &(impl HasArea + HasVolume)) - note the trait bound composition (no inheritance needed)

要求:

  1. 定义 HasArea trait,包含 fn area(&self) -> f64
  2. 定义 HasVolume trait,包含 fn volume(&self) -> f64
  3. 创建同时实现两者的 Cylinder 结构体
  4. 编写函数 fn print_shape_info(shape: &(impl HasArea + HasVolume)) 注意这里是 trait bound 的组合,不需要继承
Solution | 参考答案
use std::f64::consts::PI;

trait HasArea {
    fn area(&self) -> f64;
}

trait HasVolume {
    fn volume(&self) -> f64;
}

struct Cylinder {
    radius: f64,
    height: f64,
}

impl HasArea for Cylinder {
    fn area(&self) -> f64 {
        2.0 * PI * self.radius * (self.radius + self.height)
    }
}

impl HasVolume for Cylinder {
    fn volume(&self) -> f64 {
        PI * self.radius * self.radius * self.height
    }
}

fn print_shape_info(shape: &(impl HasArea + HasVolume)) {
    println!("Area:   {:.2}", shape.area());
    println!("Volume: {:.2}", shape.volume());
}

fn main() {
    let c = Cylinder { radius: 3.0, height: 5.0 };
    print_shape_info(&c);
}

Key insight: C# needs a 3-level hierarchy (Shape -> Shape3D -> Cylinder). Rust uses flat trait composition - impl HasArea + HasVolume combines capabilities without inheritance depth.

关键理解: C# 需要三层继承结构(Shape -> Shape3D -> Cylinder),而 Rust 用平铺的 trait 组合即可:impl HasArea + HasVolume 直接把能力组合起来,不需要继承深度。