Skip to content

trait 基础

定义共享行为:trait 声明、默认实现、常用标准库 trait 与 Deref

trait 基础

trait 的作用是声明「具备什么能力」,而不是「是什么」。它是 Rust 里唯一的接口抽象机制。

定义与实现

rust
// 声明能力:任何实现了 Summary 的类型都能给出摘要
trait Summary {
    fn summarize(&self) -> String;               // 必需方法(无默认实现)
    fn tag(&self) -> &'static str { "generic" }  // 默认方法:可以不实现
}

#[derive(Debug)]
struct Article { title: String, body: String }

#[derive(Debug)]
struct Tweet { user: String, text: String }

impl Summary for Article {
    fn summarize(&self) -> String {
        format!("{}: {}...", self.title, &self.body[..self.body.len().min(10)])
    }
    fn tag(&self) -> &'static str { "article" } // 覆盖默认实现
}

impl Summary for Tweet {
    // 只实现必需方法,tag 用默认值
    fn summarize(&self) -> String { format!("@{}: {}", self.user, self.text) }
}

fn main() {
    let a = Article { title: "Rust".into(), body: "hello world this is a body".into() };
    let t = Tweet { user: "ana".into(), text: "hi".into() };
    println!("[{}] {}", a.tag(), a.summarize());
    println!("[{}] {}", t.tag(), t.summarize());
}

输出:

text
[article] Rust: hello worl...
[generic] @ana: hi

💡 对照:Java 的 interface(Java 8+ 有 default 方法)语义几乎一致; Go 的 interface 不同 —— Go 是结构化匹配(只要方法签名对就自动满足), Rust 是名义化(nominal)匹配:必须在某个 impl 块里显式写出来,哪怕方法完全一样。

trait 作为参数:impl Trait vs 泛型 T: Trait

rust
trait Summary { fn summarize(&self) -> String; }
#[derive(Debug)] struct Article { title: String }
#[derive(Debug)] struct Tweet { user: String }
impl Summary for Article { fn summarize(&self) -> String { self.title.clone() } }
impl Summary for Tweet { fn summarize(&self) -> String { format!("@{}", self.user) } }

// 写法 A:impl Trait 参数(Rust 1.26+),调用处简洁
fn shout_a(s: &impl Summary) -> String { s.summarize().to_uppercase() }

// 写法 B:泛型参数(等价,但可以用 turbofish 显式指定、能写 where 子句)
fn shout_b<T: Summary>(s: &T) -> String { s.summarize().to_uppercase() }

// 写法 C:where 子句,bound 多时更易读
fn shout_c<T>(s: &T) -> String
where
    T: Summary + std::fmt::Debug,
{
    format!("{:?} -> {}", s, s.summarize().to_uppercase())
}

fn main() {
    let a = Article { title: "Rust".into() };
    println!("{}", shout_a(&a));
    // 写法 B 可以用 turbofish 消歧(写法 A 不行)
    println!("{}", shout_b::<Article>(&a));
    println!("{}", shout_c(&Tweet { user: "bo".into() }));
    println!("{}", shout_c(&a));
}
维度impl Trait 参数泛型 <T: Trait>
可读性简洁,一眼看出「要一个能 Summary 的东西」类型名 T 出现在签名里,稍长
能否 turbofish 指定不能能:shout_b::<Article>(&a)
能否写多个 bound能:impl Summary + Debug能:T: Summary + Debug
能否在函数体里用 T:: 关联项不能直接写 T能:T::default()<T as Trait>::X
调用多个不同类型每次调用单态化一个版本同左
where 子句配合不适用适合复杂约束(关联类型约束、生命周期)
等价性fn f(x: impl T) 完全等价于 fn f<U: T>(x: U)

🧠 原理:两者都是静态分发(static dispatch):编译器为每个具体类型生成一份专用代码 (单态化 monomorphization),调用点在编译期直接跳到具体函数,没有虚表开销。 代价是代码体积shout_b 被 20 种类型调用就会生成 20 份机器码。

⚠️ 陷阱impl Trait 参数位置 1.26 稳定,impl Trait 返回位置 1.26 稳定, 但 impl Trait 用在 trait 方法参数里(argument position impl trait, APIT)在 trait 定义中 直到 1.75 才随着 async fn in trait 一起稳定。写库时若不要求 1.75+,给 trait 方法用泛型参数更稳。

返回值位置 -> impl Trait:不能返回多种类型

rust
use std::fmt;

trait Summary { fn summarize(&self) -> String; }
struct Article { title: String }
impl Summary for Article { fn summarize(&self) -> String { self.title.clone() } }
impl fmt::Display for Article {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "<{}>", self.title) }
}

// 返回「某个实现了 Summary + Display 的类型」,具体类型由函数体决定
fn make_article(title: &str) -> impl Summary + std::fmt::Display {
    Article { title: title.to_string() }
}

fn main() {
    let a = make_article("Ownership");
    println!("{} {}", a, a.summarize()); // <Ownership> Ownership
}

-> impl Trait 表示「返回某个唯一的、我不知道名字的类型」。 编译器要求函数所有返回路径都是同一个具体类型

rust
trait Summary { fn summarize(&self) -> String; }
struct Article { title: String }
struct Tweet { user: String }
impl Summary for Article { fn summarize(&self) -> String { self.title.clone() } }
impl Summary for Tweet { fn summarize(&self) -> String { self.user.clone() } }

// 想根据条件返回 Article 或 Tweet —— 编译失败:E0308
fn pick(flag: bool) -> impl Summary {
    if flag { Article { title: "a".into() } } else { Tweet { user: "b".into() } }
}

fn main() { println!("{}", pick(true).summarize()); }
text
error[E0308]: `if` and `else` have incompatible types
  |
9 |     if flag { Article { title: "a".into() } } else { Tweet { user: "b".into() } }
  |               -----------------------------        ^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `Article`, found `Tweet`
  |
  = note: expected because of this

三种解法:

rust
// 方案 1:Box<dyn Trait> —— 引入动态分发,付出一次堆分配 + 虚表跳转
fn pick_boxed(flag: bool) -> Box<dyn Summary> {
    if flag { Box::new(Article { title: "a".into() }) }
    else { Box::new(Tweet { user: "b".into() }) }
}

// 方案 2:定义一个枚举把所有可能收拢成一个类型(推荐:保留静态分发)
enum AnySummary { Article(Article), Tweet(Tweet) }
impl Summary for AnySummary {
    fn summarize(&self) -> String {
        match self {
            AnySummary::Article(a) => a.summarize(),
            AnySummary::Tweet(t) => t.summarize(),
        }
    }
}
fn pick_enum(flag: bool) -> AnySummary {
    if flag { AnySummary::Article(Article { title: "a".into() }) }
    else { AnySummary::Tweet(Tweet { user: "b".into() }) }
}

// 方案 3:两个分支都返回同一个具体类型,只是内部数据不同
struct Either { article: Option<Article>, tweet: Option<Tweet> }

🧠 原理-> impl Trait 是「存在类型」(existential type)的语法糖: 「存在某个类型实现了 Trait,具体是什么由被调函数决定」。而分支返回不同类型要求的是 「枚举式联合」(union),二者语义不同 —— 这就是为什么方案 2 那样显式建枚举是正解。



常用标准库 trait 地图

trait关键方法用途典型实现者可 derive
Debugfmt{:?} 调试输出几乎所有 std 类型
Displayfmt{} 用户可读输出,自动带 to_string()i32&strPath
Cloneclone显式深拷贝StringVec<T>Box<T>
Copy(标记 trait)赋值即复制,原值仍可用所有标量、&T[T; N](T: Copy)✅(不能与 Drop 共存)
PartialEqeq / ne==!=f32/f64 之外几乎全部(浮点也是 PartialEq
Eq(标记 trait)严格等价关系,可作哈希键不含浮点的可比较类型
PartialOrdpartial_cmp< > <= >=,浮点返回 None 表示不可比数值、StringOption<T>
Ordcmpsort()max() 的全序整数、&strVec<T>(T: Ord)
HashhashHashMap/HashSet 的键整数、String、元组
DefaultdefaultT::default()..Default::default()数值(0)、String(空)、Vec(空)✅(枚举需 #[default] 标注,1.62+)
Fromfrom类型转换的入口,自动得到 IntoString: From<&str>i64: From<i32>
Intointox.into(),自动由 From 派生反向于 From
TryFrom / TryIntotry_from / try_into可能失败的转换,返回 Resultu16: TryFrom<u32>u8: TryFrom<i32>
AsRef / AsMutas_ref / as_mut廉价的「借出另一种视图」,常用于函数参数放宽String: AsRef<str>Path: AsRef<OsStr>
Deref / DerefMutderef / deref_mut自动解引用、方法解析链Box<T>StringVec<T>Arc<T>
Dropdrop离开作用域时的清理BoxFileMutexGuard
Iteratornext(关联类型 Item惰性序列,map/filter/fold/collectVec::iterRangeChars
IntoIteratorinto_iterItemIntoIterfor x in expr 靠它工作Vec<T>&Vec<T>HashMap
FromIteratorfrom_itercollect() 的目标Vec<T>StringHashMap
Fn / FnMut / FnOncecall闭包的三种调用能力,决定闭包能被调用几次所有闭包、函数指针
ErrorsourceDisplay + Debug错误类型统一抽象std::io::ErrorParseIntError❌(thiserror 可代写)
Send / Sync(标记 trait,unsafe跨线程传递 / 共享引用的安全性由编译器自动推导

🧠 原理Fn / FnMut / FnOnce能力递减的: 每个闭包都实现三者中的一个或多个。Fn: FnMut: FnOnce 是 supertrait 关系。 只用 &self 的闭包实现 Fn,改捕获变量的实现 FnMut,消耗捕获变量的只实现 FnOnce。 更多细节见 智能指针与闭包


trait 进阶到哪里学

关联类型、supertrait、孤儿规则、dyn Trait 与静态/动态分发这些进阶主题,统一放在 「泛型与生命周期 → trait 进阶」集中讲解, 本页只保留日常使用最多的基础部分。学完本页可以直接往下,也可以先跳去读进阶篇再回来。


Deref 与自动解引用

方法调用的 autoderef 规则链

当写 receiver.method() 时,编译器按顺序尝试:

  1. receiver 的类型本身找方法(含所有已导入 trait 的方法)。
  2. &receiver&mut receiver
  3. 若都不行,对 receiver 做一次 *receiver(通过 Deref),重复第 1~2 步,直到找到方法或耗尽。
rust
fn main() {
    let s = String::from("hello world");
    // String 本身没有 split_whitespace,但 String: Deref<Target = str>,于是找到 str 的方法
    println!("{}", s.split_whitespace().count());
    println!("{}", s.len());        // str::len

    let v = vec![1, 2, 3];
    println!("{}", v.len());        // Vec::len(固有方法,第 1 步就命中)
    println!("{}", v.first().unwrap()); // Vec::first

    let boxed = Box::new(String::from("deep"));
    // &Box<String> -> &String -> &str,两级 deref
    let borrowed: &str = &boxed;
    println!("{}", borrowed.len());
}

Deref 强制转换(deref coercion)

&T 会在需要 &U 时自动转换,条件是 T: Deref<Target = U>,且可以连续多级:

rust
fn takes_str(s: &str) -> usize { s.len() }
fn takes_slice(xs: &[i32]) -> i32 { xs.iter().sum() }

fn main() {
    let text = String::from("hello");
    let rt: &String = &text;
    let rs: &str = rt;              // &String -> &str

    let v = vec![1, 2, 3];
    let rv: &Vec<i32> = &v;
    let rsl: &[i32] = rv;           // &Vec<T> -> &[T]

    println!("{} {}", takes_str(&text), takes_slice(&v)); // 传参处自动转换

    let b: Box<[u8; 3]> = Box::new([1, 2, 3]);
    let arr: &[u8; 3] = &*b;        // 显式解引用:&Box<T> -> &T
    let sl: &[u8] = arr;            // &[u8; 3] -> &[u8](这是 unsize 转换,不是 Deref)
    println!("{:?}", sl);

    let boxed_str: Box<str> = "abc".into();
    let again: &str = &boxed_str;   // &Box<str> -> &str
    println!("{again} {rs}");
}

输出:5 6 / [1, 2, 3] / abc hello

🧠 原理&Vec<T>&[T]&[u8; 3]&[u8] 严格来说是 unsized coercionVec<T>: Deref<Target = [T]> 提供了前者,数组到切片是另一条规则)。共同点是: 它们都只发生在引用层面,绝不会把一个 String 值直接变成 str 值(str 没有大小,做不到)。

⚠️ 陷阱:deref coercion 不适用于泛型边界fn f<T: AsRef<str>>(t: T)fn f(s: &str) 在调用处都接受 &String,但前者要求显式满足 bound, 且 &String 满足 AsRef<str> 靠的是 impl AsRef<str> for String 与引用转发, 不是 deref coercion。写库时优先用 &str / impl AsRef<str> 这类宽松参数。

为什么不该为业务类型滥用 Deref

Deref 的本意是「智能指针」(smart pointer):Box<T>Rc<T>Arc<T>MutexGuard<T> 通过 Deref 表现得像它包裹的 T。它不是继承、也不是「透明包装」的通用工具。

rust
// ❌ 反例:为了省几个方法调用,让 User 伪装成 String
struct User { name: String }
impl std::ops::Deref for User {
    type Target = String;
    fn deref(&self) -> &String { &self.name }
}

fn main() {
    let u = User { name: "ana".into() };
    // 现在这些全都编译通过:
    println!("{}", u.to_uppercase());     // 看起来 User 有 to_uppercase
    println!("{}", u.len());              // len 是名字长度还是别的?
    println!("{}", u.replace("a", "b"));  // 语义完全脱离 User 的概念
    fn wants_string(s: &String) {}
    wants_string(&u);                     // 隐式转换:调用点看不出发生了什么
}

四个理由:

  1. 方法名污染User 会「获得」String 的全部方法(几十个),自动补全和文档都变得无用。
  2. 调用点不可见wants_string(&u) 里发生了隐式转换,读代码的人必须去查 Deref 实现。
  3. DerefMut 会破坏不变量:如果实现了 DerefMut,外部可以通过 *u = String::new() 直接改掉内部字段,绕过你所有的校验逻辑。
  4. 错误信息变难懂:出问题时编译器报的是 String 上的错误,而你写的是 User

正确做法:需要「访问内部」就写一个显式的方法 fn name(&self) -> &str { &self.name }; 需要「接受多种类型」就用 AsRef<str> / impl Into<String> 这样的参数放宽。 只有在实现真正的智能指针(拥有所有权、并在解引用时提供类似内部值的语义)时才实现 Deref



与其他语言的对照

同一件建模的事,在别的语言里怎么写、Rust 为什么要不一样:

关注点Python / Java / C# / JSC++GoRustRust 为什么这样
数据建模class(数据 + 行为 + 继承打包在一起)class / structstruct 默认 public)struct + 方法集struct + 独立 impl数据与行为解耦:同一个 impl 可以放在别的模块,泛型/trait 无需修改原类型定义
代码复用继承(extends / :继承 + 多继承组合 + 嵌入(embedding)只有组合trait + 泛型),没有继承继承会导致「脆弱基类」问题与菱形继承;组合 + trait bound 能在编译期表达全部复用需求且不引入隐式耦合
接口interface / 抽象基类纯虚类(无运行时检查)interface结构化,自动满足)trait名义化,必须显式 impl显式实现让「谁实现了什么」可被 cargo doc 完整枚举,也避免给第三方类型意外「加方法」
抽象类 vs 默认方法abstract class + default 方法(Java 8+)纯虚函数 + 默认实现无(靠接口组合)trait 里的默认方法 + supertrait trait A: Btrait 没有字段,所以不存在「基类状态」;需要共享状态时用组合把数据放进结构体
多重继承接口多实现(方法冲突需手动消解)允许多继承(菱形问题)接口组合多 trait 实现,冲突方法必须写 Type::method(&x) 显式消歧没有菱形继承,因为 trait 不携带数据;方法冲突在调用点而非定义点解决
枚举Java enum(本质是类的实例,可带字段但所有常量共享)enum class(只能是整数常量)const + iota(无载荷)enum 带不同载荷的变体,即 ADT用一个类型表达「且」与「或」,配合穷尽 match 把状态机错误变成编译错误
联合类型TS A | B(仅编译期) / C# 无std::variant<A, B>无(靠 interface{} + 类型断言)enum 显式列出所有可能联合是名义的,新增可能性必须改枚举定义,从而强制所有 match 同步更新
空值null / None(类型系统不区分)nullptr / 未定义行为nil 指针Option<T>,且 TOption<T> 是不同类型编译器强制你处理 None 分支;Option<&T> 还享受空指针优化,零额外开销
字符串化__str__ / toString() / ToString()operator<< 重载String() 方法Display{})与 Debug{:?})分开面向用户的格式和调试格式需求完全不同,分开后 {:?} 能自动 derive、{} 必须手写
相等与排序equals / hashCode / Comparable(易写错、易不一致)operator== / operator<无统一协议PartialEq / Eq / PartialOrd / Ord / Hash 分离浮点不满足 Eq事实,那就用类型系统表达出来,而不是假装所有类型都可比较
对象身份引用语义,a is ba == b 是两回事值语义 / 指针语义分不清值语义值语义 + 显式 & 借用;Rc::ptr_eq 才比较地址默认按值传递,比较就是比内容,== 不会意外变成比地址
鸭子类型Python / JS:能调就行,运行期报错模板:编译期报错但报错信息极长接口隐式满足trait bound:编译期检查且报错精确到方法名编译期就能确认「这个类型有没有这个方法」,错误直接指出缺失的 trait 和行号
动态派发虚方法表(默认全动态)virtual 函数接口值(含类型指针)显式 dyn Trait动态派发有成本就应该显式写出来;默认静态分发可以内联,性能可预期
可变性对象字段随时可改默认全可改结构体字段可改字段可变性由绑定 let mut 决定可变性「传染范围」越清晰,借用检查越好推理,数据竞争越难发生


常见坑与编译错误

E0599 no method found —— trait 未导入

text
error[E0599]: no method named `area` found for struct `Sq` in the current scope
 --> src/main.rs:7:22
  |
1 | mod shapes { pub trait Area { fn area(&self) -> f64; }
  |                                  ---- the method is available for `Sq` here
...
7 |     println!("{}", s.area());
  |                      ^^^^ method not found in `Sq`
  |
  = help: items from traits can only be used if the trait is in scope
help: trait `Area` which provides `area` is implemented but not in scope; perhaps you want to import it
  |
1 + use crate::shapes::Area;

原因:trait 方法只有在作用域内才能被 .method() 语法找到。这是 Rust 避免 「某个依赖悄悄给你的类型加了方法」的刻意设计。 修法use crate::shapes::Area;。找不到时想想 use std::io::Write;File::write_all) 和 use std::fmt::Write;String::write_fmt)这两对经典案例。

E0117 orphan rule

见「孤儿规则(orphan rule)与 coherence」。原因:外部 trait + 外部类型会破坏全局一致性。 修法:用 newtype 包装 struct Wrapper(Vec<i32>),或者定义自己的 trait。

E0277 trait bound not satisfied —— 含 String: Copy 这类经典误解

rust
fn need_copy<T: Copy>(v: T) -> (T, T) { (v, v) }

fn main() {
    let s = String::from("hi");
    let (a, b) = need_copy(s);
    println!("{a}{b}");
}
text
error[E0277]: the trait bound `String: Copy` is not satisfied
 --> src/main.rs:4:28
  |
4 |     let (a, b) = need_copy(s);
  |                  --------- ^ the trait `Copy` is not implemented for `String`
  |
note: required by a bound in `need_copy`

原因String 拥有堆内存,Copy 会导致「两个所有者」→ 双重释放。 修法:改 T: Clone 并显式 .clone();或者把参数改成 &str / &T; 或者接受所有权并返回它(fn need(v: T) -> (T, T) 里用 v.clone() 需要 Clone)。

⚠️ 陷阱#[derive(Clone)] 不会String 字段变得可以 CopyClone 是「显式请求复制」,Copy 是「隐式复制」,两者不可互换。

E0046 not all trait items implemented

text
error[E0046]: not all trait items implemented, missing: `bye`
 --> src/main.rs:3:1
  |
1 | trait Greet { fn hello(&self) -> String; fn bye(&self) -> String; }
  |                                          ------------------------ `bye` from trait
3 | impl Greet for P { fn hello(&self) -> String { "hi".into() } }
  | ^^^^^^^^^^^^^^^^ missing `bye` in implementation

原因:trait 里没有默认实现的方法必须在 impl 里全部给出。 修法:补上缺失方法,或在 trait 定义里给它加默认实现。注意编译器只报「缺哪些」, 不区分是你忘了还是签名写错 —— 签名不一致会变成两个不同方法,从而同时报 E0046E0407 method is not a member of trait

dyn Trait 需要指针

text
error[E0277]: the size for values of type `(dyn Render + 'static)` cannot be known
              at compilation time
 --> src/main.rs:1:12
  |
1 | fn take(d: dyn Render) -> String { d.render() }
  |            ^^^^^^^^^^ doesn't have a size known at compile-time
  |
  = help: the trait `Sized` is not implemented for `(dyn Render + 'static)`
help: function arguments must have a statically known size, borrowed types always have a known size
  |
1 | fn take(d: &dyn Render) -> String { d.render() }

原因dyn Trait 是未定大小类型。 修法&dyn Trait(借用)、Box<dyn Trait>(拥有)、Rc<dyn Trait>(共享)。 函数参数想同时接受两者,用 impl Trait(静态分发)。

impl Trait 返回值不能分支返回不同类型

见「返回值位置 -> impl Trait:不能返回多种类型」。修法Box<dyn Trait>、统一的枚举、或让两分支返回同一类型。

#[derive(Debug)] 的连锁错误

rust
struct Meters(u32);

#[derive(Debug)]
struct Trip { dist: Meters }
text
error[E0277]: `Meters` doesn't implement `Debug`
 --> src/main.rs:3:15
  |
2 | #[derive(Debug)]
  |          ----- in this derive macro expansion
3 | struct Trip { dist: Meters }
  |               ^^^^^^^^^^^^ the trait `Debug` is not implemented for `Meters`
  |
help: consider annotating `Meters` with `#[derive(Debug)]`
  |
1 + #[derive(Debug)]
2 | struct Meters(u32);

原因derive(Debug) 为每个字段生成 f.debug_struct(..).field("dist", &self.dist), 它要求每一个字段类型都实现 Debug修法:顺藤摸瓜给缺失的类型加 #[derive(Debug)];某个字段确实无法 Debug 时, 手写 impl fmt::Debug 并在 fmt 里跳过/简化该字段。

E0038 —— trait 不是 dyn 兼容的

text
error[E0038]: the trait `Service` is not dyn compatible
  |
  = note: for a trait to be dyn compatible it needs to allow building a vtable
5 |     const API_VERSION: u32;
  |           ^^^^^^^^^^^ ...because it contains associated const `API_VERSION`
  = help: consider moving `API_VERSION` to another trait

原因:泛型方法、按值 self、返回 Self、关联常量都无法进入 vtable。 修法:给相关方法加 where Self: Sized 把它们排除出 vtable;把关联常量移到别的 trait; 或改用泛型(静态分发)。注意新版 Rust 把「对象安全」(object safety)改称为 「dyn 兼容」(dyn compatibility),文档和搜索结果里两种叫法都会遇到。



速查表

下面的每一行都是一个独立语法片段(不是完整程序),用来照抄查语法:

text
// ── 结构体 ────────────────────────────────────────────
struct Named { a: u32, b: String }        // 具名字段
struct Tup(u32, String);                  // 元组结构体,访问 .0 .1
struct Unit;                              // 单元结构体,0 字节
Named { a: 1, b: String::new() }          // 字面量构造
Named { a: 1, ..base }                    // 更新语法(非 Copy 字段会被 move)
let Named { a, b } = n;                   // 解构
let Named { a, .. } = n;                  // 只取部分字段
let Named { ref a, .. } = n;              // 借用解构(注意 2024 的 match ergonomics 变化)

// ── 方法 ──────────────────────────────────────────────
impl Named { fn f(&self) {} }             // 只读
impl Named { fn f(&mut self) {} }         // 改
impl Named { fn f(self) {} }              // 消耗
impl Named { fn new() -> Self { .. } }    // 关联函数(无 self)
Self { a, b }                             // 构造自身(改名安全)

// ── derive ────────────────────────────────────────────
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
// Copy 与 Drop 互斥;Eq 不能用于含 f64 的类型;枚举 Default 需 #[default]

// ── 打印 ──────────────────────────────────────────────
println!("{:?} {:#?}", x, x);             // Debug(可 derive)
println!("{}", x);                        // Display(手写 impl fmt::Display)
write!(f, "{}", v)?;                      // Display 里追加内容
impl std::fmt::Display for T {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "") }
}

// ── 枚举与 match ──────────────────────────────────────
enum E { A, B(i32), C { x: i32 } }        // 单元 / 元组 / 结构体变体
match e { E::A => .., E::B(n) => .., E::C { x } => .. }   // 必须穷尽
if let E::B(n) = e { .. }                 // 单变体
while let Some(x) = it.next() { .. }      // 循环取出
let Ok(v) = r else { return };            // 1.65+,失败分支必须发散
matches!(e, E::A)                         // 返回 bool 的 match
n @ 1..=9                                 // 绑定 + 范围
Some(ref s) | None                        // 注意 2024 edition 的 ref 限制

// ── Option ────────────────────────────────────────────
opt.map(f) / and_then(f) / filter(p)      // 变换 / 链式 / 过滤
opt.unwrap_or(d) / unwrap_or_else(f)      // 默认值(立即 / 惰性)
opt.as_ref() / as_deref()                 // 借用视图
opt.ok_or(e) / ok_or_else(f)              // 转 Result
opt.take()                                // 取出,自身变 None
opt?                                      // None 时提前返回(返回 Option 的函数里)

// ── trait ─────────────────────────────────────────────
trait T { fn req(&self); fn opt(&self) {} }   // 必需 + 默认方法
impl T for X { fn req(&self) {} }             // 实现
trait T: Base { .. }                          // supertrait
trait T { type Out; const N: u32; }           // 关联类型 + 关联常量
fn f(x: impl T) / fn f<U: T>(x: U)            // 静态分发两写法
fn f(x: &dyn T) / Box<dyn T>                  // 动态分发
where U: T + Clone + 'a                        // where 子句


内容以 rustc 1.98.1 · Rust 2024 edition 为基准