关联函数
本教程共 78 篇 · 第 28 篇 · 更新于 2026-08-08 · 约 10 分钟阅读
本节目标:认识写在 impl 块里但不带
self的”关联函数”(典型如构造函数new),学会用类型::函数()调用,分清它和方法的区别,并理解Self与from风格。
上一章的方法第一个参数是 self,要通过实例才能调用(rect.area())。但有时你想要一个”和类型相关、却不依赖某个具体实例”的函数——比如”造一个新的矩形""算这个类型允许的最大尺寸”。这种函数叫关联函数(associated function),因为它关联到类型本身,而非某个实例。它是 Rust 里”构造函数”和”类型级工具”的载体。
1-1 关联函数长啥样
它写在 impl 块里,但没有 self 参数:
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn new(width: u32, height: u32) -> Rectangle {
Rectangle { width, height }
}
}
fn main() {
let rect = Rectangle::new(30, 50);
println!("{} x {}", rect.width, rect.height);
}
new 没有 self,所以它是关联函数。调用用 类型::函数() 的双冒号语法:Rectangle::new(30, 50)。它返回一个新造好的 Rectangle,常用来当构造函数。注意它不依赖任何实例——你此刻还没有”矩形”呢,正是靠它才造出第一个矩形。
Note双冒号
::是”路径”运算符,表示”去这个类型下面找那个函数”。Rectangle::new读作”Rectangle 类型下的 new 函数”。你之前用的String::from也是关联函数,同理Vec::new()、HashMap::new()全是。凡是见到类型::形式的调用,十有八九是在调关联函数。
1-2 为什么叫”构造函数”惯例
Rust 没有别的语言那种 new 关键字或构造器语法。社区约定:把”创建实例”的关联函数命名为 new,于是 Rectangle::new(...)、Vec::new() 这种写法到处都是。它只是个普通函数,叫 new 是约定俗成,不是语言强制。
你也可以叫别的名字,比如专门造正方形:
impl Rectangle {
fn square(size: u32) -> Rectangle {
Rectangle { width: size, height: size }
}
}
fn main() {
let sq = Rectangle::square(20); // 20x20
}
Rectangle::square(20) 返回一个正方形。这种”语义化构造”比硬塞参数清晰——调用方一看就知道造的是正方形,不用去猜”两个参数一样是不是正方形的意思”。当一种类型有多种常见构造方式时,用多个具名关联函数(new / square / from_xxx)比一个参数繁多的 new 更可读。
1-3 关联函数 vs 方法
| 对比 | 关联函数 | 方法 |
|---|---|---|
有无 self 参数 | 无 | 有(&self / &mut self / self) |
| 调用方式 | 类型::函数() | 实例.方法() |
| 是否依赖实例 | 否 | 是 |
| 典型用途 | 构造函数、工具函数 | 操作具体实例的数据 |
一句话:需要”某个具体对象”才能干的事用方法;属于”这个类整体”、用来造对象或做通用计算的用关联函数。两者可以和平共处——同一个 impl 块里既有方法又有属性函数,Rust 完全接受。
Warning新手容易在关联函数里误用
self——关联函数根本没有self可用,想访问实例字段会报错。如果逻辑需要实例,就改成方法(加&self);如果只是构造,就返回Self(代表当前类型)。记住:self只在方法里存在,关联函数签名里写self直接编译失败。
1-4 Self 关键字
关联函数常返回 Self,它是”当前正在 impl 的这个类型”的别名:
impl Rectangle {
fn new(w: u32, h: u32) -> Self {
Self { width: w, height: h } // Self 即 Rectangle
}
}
Self(首字母大写)指代 Rectangle,比手写类型名更灵活——以后类型改名,函数内部不用跟着改。构造时 Self { ... } 和 Rectangle { ... } 等价。在关联函数里用 Self 是惯例,既省打字,又让代码对改名更鲁棒。
Tip除了返回
Self,Self也能用在参数里,比如fn merge(&self, other: Self)表示参数是”同类型的另一个实例”。当你写的逻辑”吃掉”或”接收”一个同类实例时,用Self参数比重复写类型名更优雅。
1-5 关联函数也能当工具
不只能构造,还可以是独立于实例的辅助计算:
impl Rectangle {
fn max_dimension() -> u32 {
100 // 比如业务规定的上限
}
}
fn main() {
println!("允许的最大边长:{}", Rectangle::max_dimension());
}
Rectangle::max_dimension() 和任何实例无关,纯粹是挂在类型下的工具。当逻辑”属于这个类型、但不需要某个对象”时,放关联函数最合适。比如一些常量上限、单位换算、工厂选择,都是它的用武之地。它把”和这个类型相关的计算”集中到了类型自己身上,调用方一眼就知道去哪找。
Tip看到
类型::某某()的调用(如String::from、Vec::new、HashMap::new),那一定是在调关联函数,不是方法。记住双冒号::是关联函数/模块路径的标志,单点.才是方法。这一眼看穿的技巧,能帮你立刻判断”这东西需不需要先有实例”。
1-6 from 风格的关联函数
关联函数返回 Self 时,如果构造逻辑要从另一种类型转换过来,常写成 from_xxx 或直接 from 风格:
impl Rectangle {
fn from_dimensions(w: u32, h: u32) -> Self {
Self { width: w, height: h }
}
fn square(size: u32) -> Self {
Self { width: size, height: size }
}
}
fn main() {
let r1 = Rectangle::from_dimensions(10, 20);
let r2 = Rectangle::square(15);
}
两个函数都返回 Self,调用都是 Rectangle::xxx(...) 这种”类型::“形式。它们和 String::from 思路一致——from 系列关联函数本质是”从别的东西造出我”。这让”从 A 造 B”的语义读起来很自然:Rectangle::from_dimensions(10, 20) 一看就是”用宽高造矩形”。
Note标准库里有个通用的
Fromtrait,约定用Type::from(其它类型)做转换,比如String::from("hi")就是实现了From<&str>。当你写自己的类型、且”能从某类型转换而来”有清晰语义时,进阶阶段可以实现Fromtrait,还能白捡一个.into()转换语法。现阶段先理解”关联函数 = 挂在类型下的工具函数、常用作构造”即可。
1-7 你可能会踩的坑
- 坑一:用
.去调用关联函数,写rect.new()会报错——关联函数不绑实例,必须用类型::new()。 - 坑二:在关联函数里写
self.width,编译报错说找不到self。关联函数里没有实例,要访问字段就改成方法,或先把实例造出来当参数传。 - 坑三:忘记关联函数也要写在
impl里,写成独立的fn new(),结果它不是”Rectangle 的”函数,调用时也没有Rectangle::前缀。
Warning别被”构造函数”这名字误导,以为
new是 Rust 的关键字或特殊语法。它就是一个普通关联函数,叫make、create、build都一样能跑。new只是社区习惯,编译器一视同仁。理解这点,你就不会去纠结”为什么我的 new 不加参数也行”——因为它本就是个普通函数,参数写几个、写不写都由你定。
1-8 关联函数与方法并存于同一 impl
一个 impl 块里可以同时躺着”方法”(带 self)和”关联函数”(不带 self),它们互不冲突,只是调用语法不同:方法用 实例.方法(),关联函数用 类型::函数()。这种并存很自然——构造函数 new 是关联函数(还没实例呢,怎么可能有 self),而 area、width 这类操作已有实例的是方法。把”造对象”和”用对象”的逻辑放在同一个 impl 里,代码聚拢、好找。
Note一个类型可以有多个
impl块,不强制写在一处。你可以把方法按主题拆到几个impl块里,编译器最后会合并看待。但惯用法是:构造函数(new等关联函数)和核心方法放一起,保持紧凑。进阶后你还会遇到”为别人定义的类型实现 trait”的impl,那时impl会写到别的地方,但基础语法一致。
关联函数不只是构造函数。任何”挂在类型名下、不需要具体实例”的实用工具都能写成关联函数:比如 f64::abs(-3.0) 求绝对值、Vec::new() 造空向量、String::from("x") 从别的东西造字符串。它们本质上是”名字空间里的函数”,靠 :: 把函数归类到某个类型之下,避免全局名字冲突、也让调用者一眼看出”这函数属于谁”。
Tip当你纠结”这个函数该放哪”:如果它的输入里已经有一个该类型的实例、逻辑围绕那个实例展开,写成方法(
&self);如果它是”凭空造一个实例”或”做类型级的通用计算”(如单位换算、默认配置),写成关联函数。命名上,构造函数用new,从别的类型转换来的用from,这是社区心照不宣的约定,写出来别人秒懂。
另外注意:关联函数调用用 双冒号::,方法调用用 点.,别混。写 Rectangle.new() 是错的(那是方法调用语法,但 new 不是方法),必须写 Rectangle::new()。这个符号差异是 Rust 区分”类型级调用”和”实例级调用”的直观体现。
1-9 小结
关联函数写在 impl 里、无 self、用 类型::函数() 调用;new 是构造函数的约定名;返回 Self 表示当前类型。它和方法互补:方法操作实例,关联函数造实例或做类型级工具。判断该用哪个很简单——逻辑需要具体实例就用方法,纯粹造对象或类型级计算就用关联函数。下一章进入枚举(enum)——Rust 表达”多选一”数据的利器,配合后面的 match 模式匹配,能力非常强。