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

Traits vs Duck Typing
Trait 与鸭子类型的对照

What you’ll learn: Traits as explicit contracts, how Protocol (PEP 544) relates to traits, generic bounds with where, trait objects versus static dispatch, and the most important standard-library traits.
本章将学习: trait 如何把行为契约显式写出来,Protocol(PEP 544)和 trait 的关系,where 子句里的泛型约束,trait object 与静态分发的差异,以及标准库里最重要的一批 trait。

Difficulty: 🟡 Intermediate
难度: 🟡 进阶

This is one of the places where Rust’s type system starts to feel genuinely powerful to Python developers. Python says “if it quacks like a duck, use it.” Rust says “tell me exactly which duck behaviors are required, and I will verify them before the program runs.”
这一章往往是 Python 开发者第一次真正感受到 Rust 类型系统威力的地方。Python 的思路是“像鸭子就拿来用”;Rust 的思路是“把需要的鸭子行为写清楚,然后在程序运行前全部验证完”。

Python Duck Typing
Python 的鸭子类型

# Python — duck typing: anything with the right methods works
def total_area(shapes):
    """Works with anything that has an .area() method."""
    return sum(shape.area() for shape in shapes)

class Circle:
    def __init__(self, radius): self.radius = radius
    def area(self): return 3.14159 * self.radius ** 2

class Rectangle:
    def __init__(self, w, h): self.w, self.h = w, h
    def area(self): return self.w * self.h

# Works at runtime — no inheritance needed!
shapes = [Circle(5), Rectangle(3, 4)]
print(total_area(shapes))  # 90.54

class Dog:
    def bark(self): return "Woof!"

total_area([Dog()])  # 💥 AttributeError

Rust Traits — Explicit Duck Typing
Rust trait:显式版鸭子类型

#![allow(unused)]
fn main() {
trait HasArea {
    fn area(&self) -> f64;
}

struct Circle { radius: f64 }
struct Rectangle { width: f64, height: f64 }

impl HasArea for Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * self.radius * self.radius
    }
}

impl HasArea for Rectangle {
    fn area(&self) -> f64 {
        self.width * self.height
    }
}

fn total_area(shapes: &[&dyn HasArea]) -> f64 {
    shapes.iter().map(|s| s.area()).sum()
}

let shapes: Vec<&dyn HasArea> = vec![&Circle { radius: 5.0 }, &Rectangle { width: 3.0, height: 4.0 }];
println!("{}", total_area(&shapes));

// struct Dog;
// total_area(&[&Dog {}]);  // ❌ Compile error
}

Key insight: Python’s duck typing gives flexibility by postponing checks to runtime. Rust keeps much of that flexibility, but moves the verification to compile time.
关键理解: Python 的鸭子类型是把灵活性建立在“运行时再检查”之上;Rust 则试图保留这份灵活性,但把校验提前到编译期。


Protocols (PEP 544) vs Traits
Protocol(PEP 544)与 trait

Python’s Protocol is probably the closest built-in concept to Rust traits. The biggest difference is enforcement: Python mostly relies on tooling, while Rust makes the compiler负责到底。
Python 的 Protocol 大概是最接近 Rust trait 的概念。最大的差别还是执行力度:Python 主要依赖工具链提醒,Rust 则让编译器亲自负责到底。

Python Protocol
Python 的 Protocol

from typing import Protocol, runtime_checkable

@runtime_checkable
class Printable(Protocol):
    def to_string(self) -> str: ...

class User:
    def __init__(self, name: str):
        self.name = name
    def to_string(self) -> str:
        return f"User({self.name})"

class Product:
    def __init__(self, name: str, price: float):
        self.name = name
        self.price = price
    def to_string(self) -> str:
        return f"Product({self.name}, ${self.price:.2f})"

def print_all(items: list[Printable]) -> None:
    for item in items:
        print(item.to_string())

Rust Trait
Rust 的 trait

#![allow(unused)]
fn main() {
trait Printable {
    fn to_string(&self) -> String;
}

struct User { name: String }
struct Product { name: String, price: f64 }

impl Printable for User {
    fn to_string(&self) -> String {
        format!("User({})", self.name)
    }
}

impl Printable for Product {
    fn to_string(&self) -> String {
        format!("Product({}, ${:.2})", self.name, self.price)
    }
}

fn print_all(items: &[&dyn Printable]) {
    for item in items {
        println!("{}", item.to_string());
    }
}

// print_all(&[&42i32]);  // ❌ Compile error
}

Comparison Table
对照表

Feature
特性
Python ProtocolRust Trait
Structural typing
结构化类型
✅ Implicit
隐式
❌ Explicit impl
必须显式实现
Checked at
检查时机
Runtime or mypy
运行时或静态工具
Compile time
编译期
Default implementations
默认实现
Can add to foreign types
能否扩展外部类型
✅ Within orphan rules
在孤儿规则约束内可以
Multiple protocols / traits
多协议 / 多 trait
Associated types
关联类型
Generic constraints
泛型约束

Generic Constraints
泛型约束

Python Generics
Python 泛型

from typing import TypeVar, Sequence

T = TypeVar('T')

def first(items: Sequence[T]) -> T | None:
    return items[0] if items else None

from typing import SupportsFloat
T = TypeVar('T', bound=SupportsFloat)

def average(items: Sequence[T]) -> float:
    return sum(float(x) for x in items) / len(items)

Rust Generics with Trait Bounds
Rust:带 trait bound 的泛型

#![allow(unused)]
fn main() {
fn first<T>(items: &[T]) -> Option<&T> {
    items.first()
}

fn average<T>(items: &[T]) -> f64
where
    T: Into<f64> + Copy,
{
    let sum: f64 = items.iter().map(|&x| x.into()).sum();
    sum / items.len() as f64
}

fn log_and_clone<T: std::fmt::Display + std::fmt::Debug + Clone>(item: &T) -> T {
    println!("Display: {}", item);
    println!("Debug: {:?}", item);
    item.clone()
}

fn print_it(item: &impl std::fmt::Display) {
    println!("{}", item);
}
}

Generics Quick Reference
泛型速查表

PythonRustNotes
说明
TypeVar('T')<T>Unbounded generic
无约束泛型
TypeVar('T', bound=X)<T: X>Bounded generic
有约束泛型
Union[int, str]enum or trait objectNo direct union type
没有直接等价的联合类型
Sequence[T]&[T]Borrowed sequence
借用切片
Callable[[A], R]Fn(A) -> RFunction trait
函数 trait
Optional[T]Option<T>Built into the language
语言内建

Common Standard Library Traits
常见标准库 trait

These are the Rust equivalents of many familiar Python magic methods. They describe how a type prints, compares, clones, iterates, and overloads operators.
这一组 trait 基本可以看成 Rust 世界里对很多 Python 魔术方法的对应抽象。类型怎么打印、怎么比较、怎么克隆、怎么迭代、怎么做运算符重载,基本都落在这里。

Display and Debug
DisplayDebug

#![allow(unused)]
fn main() {
use std::fmt;

#[derive(Debug)]
struct Point { x: f64, y: f64 }

impl fmt::Display for Point {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "({}, {})", self.x, self.y)
    }
}
}

Comparison Traits
比较相关 trait

#![allow(unused)]
fn main() {
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
struct Student {
    name: String,
    grade: i32,
}

let mut students = vec![
    Student { name: "Charlie".into(), grade: 85 },
    Student { name: "Alice".into(), grade: 92 },
];
students.sort();
}

Iterator Trait
Iterator trait

#![allow(unused)]
fn main() {
struct Countdown { value: i32 }

impl Iterator for Countdown {
    type Item = i32;

    fn next(&mut self) -> Option<Self::Item> {
        if self.value > 0 {
            self.value -= 1;
            Some(self.value + 1)
        } else {
            None
        }
    }
}

for n in (Countdown { value: 5 }) {
    println!("{n}");
}
}

Common Traits at a Glance
常见 trait 一览

Rust TraitPython EquivalentPurpose
用途
Display__str__Human-readable string
面向人类的显示字符串
Debug__repr__Debug string
调试输出
Clonecopy.deepcopyDeep copy
显式深拷贝
CopyImplicit scalar copy
标量自动复制
Cheap implicit copy
PartialEq / Eq__eq__Equality comparison
相等性比较
PartialOrd / Ord__lt__ etc.Ordering
排序比较
Hash__hash__Hash support
可哈希
DefaultDefault constructor pattern
默认值模式
Default initialization
From / IntoConversion constructors
转换构造
Type conversion
Iterator__iter__ / __next__Iteration
Drop__del__ / context cleanup
析构或清理
Cleanup
Add, Sub, Mul__add__ etc.Operator overloading
运算符重载
Index__getitem__[] indexing
DerefNo close equivalent
无直接对应物
Smart-pointer deref
Send / SyncNo Python equivalent
Python 无对应概念
Thread-safety markers
flowchart TB
    subgraph Static ["Static Dispatch<br/>静态分发"]
        G["fn notify(item: &impl Summary)"] --> M1["Compiled: notify_Article()"]
        G --> M2["Compiled: notify_Tweet()"]
        M1 --> O1["Inlined, zero-cost<br/>内联、零额外开销"]
        M2 --> O2["Inlined, zero-cost<br/>内联、零额外开销"]
    end
    subgraph Dynamic ["Dynamic Dispatch<br/>动态分发"]
        D["fn notify(item: &dyn Summary)"] --> VT["vtable lookup<br/>查 vtable"]
        VT --> I1["Article::summarize()"]
        VT --> I2["Tweet::summarize()"]
    end
    style Static fill:#d4edda
    style Dynamic fill:#fff3cd

Python equivalent: Python always uses dynamic dispatch at runtime. Rust defaults to static dispatch, where the compiler specializes code for each concrete type. dyn Trait is the opt-in form when runtime polymorphism is actually needed.
和 Python 的对照:Python 基本一直在做运行时动态分发;Rust 默认走静态分发,也就是编译器针对每个具体类型生成专门代码。只有确实需要运行时多态时,才主动使用 dyn Trait

📌 See also: Ch. 11 — From/Into Traits for the conversion traits in more depth.
📌 延伸阅读: 第 11 章——From/Into Traits 会继续展开转换类 trait。

Associated Types
关联类型

Rust traits can declare associated types, which each implementor fills in with a concrete type. Python has no equally strong built-in mechanism for this.
Rust 的 trait 可以声明关联类型,由每个实现者填入具体类型。Python 没有一个同等强度、由语言层统一约束的对应机制。

#![allow(unused)]
fn main() {
trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
}

struct Countdown { remaining: u32 }

impl Iterator for Countdown {
    type Item = u32;
    fn next(&mut self) -> Option<u32> {
        if self.remaining > 0 {
            self.remaining -= 1;
            Some(self.remaining)
        } else {
            None
        }
    }
}
}

Operator Overloading: __add__impl Add
运算符重载:__add__impl Add

class Vec2:
    def __init__(self, x, y):
        self.x, self.y = x, y
    def __add__(self, other):
        return Vec2(self.x + other.x, self.y + other.y)
#![allow(unused)]
fn main() {
use std::ops::Add;

#[derive(Debug, Clone, Copy)]
struct Vec2 { x: f64, y: f64 }

impl Add for Vec2 {
    type Output = Vec2;
    fn add(self, rhs: Vec2) -> Vec2 {
        Vec2 { x: self.x + rhs.x, y: self.y + rhs.y }
    }
}

let a = Vec2 { x: 1.0, y: 2.0 };
let b = Vec2 { x: 3.0, y: 4.0 };
let c = a + b;
}

The big difference is type checking. Python’s __add__ accepts whatever gets passed in and then may fail at runtime. Rust’s Add implementation fixes the operand types up front unless additional overloads are explicitly written.
最大的差别还是类型检查。Python 的 __add__ 先收下参数再说,类型不合适时运行时炸;Rust 的 Add 在实现时就把操作数类型钉死了,除非额外显式写更多重载。


Exercises
练习

🏋️ Exercise: Generic Summary Trait
🏋️ 练习:通用 Summary trait

Challenge: Define a trait Summary with fn summarize(&self) -> String, implement it for Article and Tweet, and then write fn notify(item: &impl Summary) to print the summary.
挑战:定义一个 Summary trait,要求包含 fn summarize(&self) -> String;为 ArticleTweet 实现它;然后再写一个 fn notify(item: &impl Summary) 用来打印摘要。

🔑 Solution
🔑 参考答案
trait Summary {
    fn summarize(&self) -> String;
}

struct Article { title: String, body: String }
struct Tweet { username: String, content: String }

impl Summary for Article {
    fn summarize(&self) -> String {
        format!("{} — {}...", self.title, &self.body[..20.min(self.body.len())])
    }
}

impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("@{}: {}", self.username, self.content)
    }
}

fn notify(item: &impl Summary) {
    println!("📢 {}", item.summarize());
}

fn main() {
    let article = Article {
        title: "Rust is great".into(),
        body: "Here is why Rust beats Python for systems...".into(),
    };
    let tweet = Tweet {
        username: "rustacean".into(),
        content: "Just shipped my first crate!".into(),
    };
    notify(&article);
    notify(&tweet);
}

Key takeaway: &impl Summary is close in spirit to saying “anything that satisfies this protocol”, but Rust turns that promise into a compile-time guarantee rather than a runtime gamble.
核心收获: &impl Summary 的精神很接近“任何满足这个协议的东西都行”,但 Rust 会把这个承诺变成编译期保证,而不是留到运行时碰碰运气。