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 |
|---|---|---|---|---|
Debug | fmt | {:?} 调试输出 | 几乎所有 std 类型 | ✅ |
Display | fmt | {} 用户可读输出,自动带 to_string() | i32、&str、Path | ❌ |
Clone | clone | 显式深拷贝 | String、Vec<T>、Box<T> | ✅ |
Copy | (标记 trait) | 赋值即复制,原值仍可用 | 所有标量、&T、[T; N](T: Copy) | ✅(不能与 Drop 共存) |
PartialEq | eq / ne | ==、!= | 除 f32/f64 之外几乎全部(浮点也是 PartialEq) | ✅ |
Eq | (标记 trait) | 严格等价关系,可作哈希键 | 不含浮点的可比较类型 | ✅ |
PartialOrd | partial_cmp | < > <= >=,浮点返回 None 表示不可比 | 数值、String、Option<T> | ✅ |
Ord | cmp | sort()、max() 的全序 | 整数、&str、Vec<T>(T: Ord) | ✅ |
Hash | hash | 作 HashMap/HashSet 的键 | 整数、String、元组 | ✅ |
Default | default | T::default()、..Default::default() | 数值(0)、String(空)、Vec(空) | ✅(枚举需 #[default] 标注,1.62+) |
From | from | 类型转换的入口,自动得到 Into | String: From<&str>、i64: From<i32> | ❌ |
Into | into | x.into(),自动由 From 派生 | 反向于 From | ❌ |
TryFrom / TryInto | try_from / try_into | 可能失败的转换,返回 Result | u16: TryFrom<u32>、u8: TryFrom<i32> | ❌ |
AsRef / AsMut | as_ref / as_mut | 廉价的「借出另一种视图」,常用于函数参数放宽 | String: AsRef<str>、Path: AsRef<OsStr> | ❌ |
Deref / DerefMut | deref / deref_mut | 自动解引用、方法解析链 | Box<T>、String、Vec<T>、Arc<T> | ❌ |
Drop | drop | 离开作用域时的清理 | Box、File、MutexGuard | ❌ |
Iterator | next(关联类型 Item) | 惰性序列,map/filter/fold/collect | Vec::iter、Range、Chars | ❌ |
IntoIterator | into_iter(Item、IntoIter) | for x in expr 靠它工作 | Vec<T>、&Vec<T>、HashMap | ❌ |
FromIterator | from_iter | collect() 的目标 | Vec<T>、String、HashMap | ❌ |
Fn / FnMut / FnOnce | call | 闭包的三种调用能力,决定闭包能被调用几次 | 所有闭包、函数指针 | ❌ |
Error | source、Display + Debug | 错误类型统一抽象 | std::io::Error、ParseIntError | ❌(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() 时,编译器按顺序尝试:
- 用
receiver的类型本身找方法(含所有已导入 trait 的方法)。 - 试
&receiver、&mut receiver。 - 若都不行,对
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 coercion (Vec<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); // 隐式转换:调用点看不出发生了什么
}四个理由:
- 方法名污染:
User会「获得」String的全部方法(几十个),自动补全和文档都变得无用。 - 调用点不可见:
wants_string(&u)里发生了隐式转换,读代码的人必须去查Deref实现。 DerefMut会破坏不变量:如果实现了DerefMut,外部可以通过*u = String::new()直接改掉内部字段,绕过你所有的校验逻辑。- 错误信息变难懂:出问题时编译器报的是
String上的错误,而你写的是User。
正确做法:需要「访问内部」就写一个显式的方法 fn name(&self) -> &str { &self.name }; 需要「接受多种类型」就用 AsRef<str> / impl Into<String> 这样的参数放宽。 只有在实现真正的智能指针(拥有所有权、并在解引用时提供类似内部值的语义)时才实现 Deref。
与其他语言的对照
同一件建模的事,在别的语言里怎么写、Rust 为什么要不一样:
| 关注点 | Python / Java / C# / JS | C++ | Go | Rust | Rust 为什么这样 |
|---|---|---|---|---|---|
| 数据建模 | class(数据 + 行为 + 继承打包在一起) | class / struct(struct 默认 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: B | trait 没有字段,所以不存在「基类状态」;需要共享状态时用组合把数据放进结构体 |
| 多重继承 | 接口多实现(方法冲突需手动消解) | 允许多继承(菱形问题) | 接口组合 | 多 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>,且 T 与 Option<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 b 与 a == 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字段变得可以Copy。Clone是「显式请求复制」,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 定义里给它加默认实现。注意编译器只报「缺哪些」, 不区分是你忘了还是签名写错 —— 签名不一致会变成两个不同方法,从而同时报 E0046 和 E0407 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 子句