路径(Paths)与 use 关键字
本教程共 78 篇 · 第 34 篇 · 更新于 2026-08-08 · 约 9 分钟阅读
本节目标:学会用绝对路径和相对路径引用模块里的项,并用
use把长路径收短,避免满屏都是crate::xxx::xxx。
模块建好了,怎么从别处调到里面的函数?答案和找文件一样——靠路径。这一章把路径和 use 一起讲透。
1-1 两种路径:绝对与相对
要调用一个模块里的项,得先知道它的路径。Rust 里路径有两类:
- 绝对路径:从包根开始,以
crate或外部 crate 名打头; - 相对路径:从当前模块开始,以
self、super或当前模块的标识符打头。
每一层之间用 :: 隔开。沿用餐厅的例子:
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
pub fn eat_at_restaurant() {
// 绝对路径:从包根 crate 开始
crate::front_of_house::hosting::add_to_waitlist();
// 相对路径:从当前模块(包根)开始
front_of_house::hosting::add_to_waitlist();
}
绝对路径 crate::front_of_house::hosting::add_to_waitlist() 完全顺着模块树的层级往下走,和文件系统里 /front_of_house/hosting/add_to_waitlist 一个道理——crate 就相当于根目录 /。
1-2 绝对还是相对,怎么选
两种写法都能用,但真要挪动代码位置时就分出高下了。原则只有一个:尽量让代码移动时,需要改的引用最少。
假设把 front_of_house 和 eat_at_restaurant 一起挪进一个新模块 customer_experience。如果用的是相对路径,因为两者的相对位置没变,引用不用改;如果用的是绝对路径,就得改成 crate::customer_experience::front_of_house::hosting::add_to_waitlist()。
反过来说,只把 eat_at_restaurant 移进 dining 模块,相对路径要改,绝对路径却不用动。
Tip拿不准时,优先用绝对路径。因为”调用的地方”和”定义的地方”通常是分开的,而定义的位置往往更稳,不动窝。绝对路径跟着定义走,更不容易跟着调用方一起乱。
1-3 用 super 和 self 走相对路
相对路径除了用模块名开头,还有两个特殊开头:
super表示”父模块”,等价于文件系统里的..;self表示”当前模块自己”。
super 的常见场景是子模块调用父模块的东西:
fn serve_order() {}
mod back_of_house {
fn fix_incorrect_order() {
cook_order();
super::serve_order(); // 调到父模块里的 serve_order
}
fn cook_order() {}
}
这里 super::serve_order() 就相当于”去上一层找 serve_order”。如果确定这种层级关系以后不变,super 比 crate::serve_order 更稳,将来它们都不在包根了,这条引用也不用改。
self 一般用于 use 语句里指代当前模块,单独调用自己模块的项时直接写名字就行,用 self 反而多余,所以 self 大多和 use 搭配出现,后面会看到。
1-4 use 把长路径收短
如果每次调用都写 crate::front_of_house::hosting::add_to_waitlist(),代码会又长又丑。Rust 提供 use 关键字,把路径提前引入当前作用域,之后就能省掉前缀。
引入模块和引入函数,写法略有区别:
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
// 引入模块
use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
// 或者,直接引入函数
use crate::front_of_house::hosting::add_to_waitlist;
pub fn another() {
add_to_waitlist();
add_to_waitlist();
}
Note引入模块还是引入函数?经验法则:优先引入”最细粒度”的函数或结构体,这样调用处最干净。但有两个情况改引入模块更好——同一个模块要引多个函数,或者当前作用域里已经有同名函数。比如用
HashMap通常直接use std::collections::HashMap,因为只需要这一个类型。
1-5 as 给引入项起别名
两个不同模块里有个同名类型,同时引入会冲突。比如标准库里 std::fmt::Result 和 std::io::Result 都叫 Result。解决办法有两个。
一是保留模块前缀调用:
use std::fmt;
use std::io;
fn function1() -> fmt::Result { /* ... */ }
fn function2() -> io::Result<()> { /* ... */ }
二是用 as 起个别名,一劳永逸:
use std::fmt::Result;
use std::io::Result as IoResult;
fn function1() -> Result { /* ... */ } // 这是 fmt::Result
fn function2() -> IoResult<()> { /* ... */ } // 这是 io::Result
as 给 std::io::Result 换了个名字 IoResult,冲突立刻消失。
1-6 pub use 再导出
用 use 引入一个项时,它在当前模块里默认是私有的——只有你自己能用,外面还调不到。如果你希望别人也能通过你的模块调到它,就得 pub use,把引入的项再”导出”出去:
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
pub use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
pub use 的典型用途是”统一门面”:你把自己的内部实现藏在深层模块,然后在顶层用 pub use 把它们集中再导出,用户只从一个入口拿 API,完全不用管底下怎么分层。
1-7 用 {} 和 * 批量引入
引入同一父模块下的多个项,可以收进一对花括号,少写很多 use:
use std::collections::{HashMap, BTreeMap, HashSet};
use std::{cmp::Ordering, io};
如果同时要引入某模块本身和里面的项,用 self:
use std::io::{self, Write}; // 引入 io 模块,以及 io 下的 Write
还有个偷懒写法 * 一次引入模块下所有公共项:
use std::collections::*;
Warning
*通配引入要小心。你没法一眼看清到底引入了哪些名字,很容易和自己定义的类型撞车。它适合快速写测试,正式代码里尽量少用,避免埋雷。
1-8 路径到底在找什么:模块也是“项”
理解 use 的关键,是先理解”路径在解析什么”。在 Rust 里,模块、函数、结构体、枚举、常量、trait 都叫”项(item)“。路径 crate::a::b::foo 从头往尾一级级找,最后一步 foo 通常是一个函数或结构体。
正因为模块本身也是项,所以你可以 use crate::a::b; 把一个模块整体引入,之后用 b::foo() 的方式调用;也可以直接 use crate::a::b::foo; 把最末端那个函数引入。两种写法都对,区别只是”引入到哪一层”。
绝对路径以 crate:: 开头,从 crate 根出发,重构时移动文件不容易断;相对路径用 self、super 省略前缀,写起来短,但模块搬家时可能要改。经验法则是:在模块内部引用”兄弟或父级”用相对更顺手,跨越大距离引用用绝对更稳。
Note如果一个名字被
use引入了两次(比如从不同路径引入两个同名函数),编译器会报冲突,这时用as起别名就能区分开。路径解析的本质,就是在一棵模块树上定位某个具体的项。
1-9 通配符 * 与 glob 引入的取舍
use 还能一次性引入一个路径下的所有公开项,写法是在末尾加 *:
use std::collections::*; // 把 HashMap、HashSet 等一次性引入
这叫”glob 引入”。它在两种场景很方便:一是写示例、写测试时不想逐行 use;二是为某个 trait 引入所有方法(比如 use std::io::prelude::*; 让读写方法到处可用)。
但正式项目里要谨慎。通配符会一次性把很多名字带进当前作用域,容易引起”这个名字到底从哪来”的困惑,也可能和未来新增的同名项冲突。经验法则是:具体项用具体 use,只有”方法增强型”的 prelude 才用 *。
Note真正维护代码时,优先写清每一个
use的目标,让读者一眼看出依赖来自哪里。*省了打字,却把”可读性”欠下了。
1-10 小结
这一章的路径与 use 是日常写 Rust 最高频的操作:
- 绝对路径从
crate起,相对路径从self/super/模块名起,用::分层; - 拿不准就优先绝对路径,更抗代码移动;
use收短路径;同名冲突用as起别名;pub use把内部项再导出,对外提供统一接口;{}批量引入,*通配引入要慎用。
下一章讲怎么把模块拆到不同文件里,让大工程不再挤在一个文件中。