目录
- Rust PathBuf 使用详解
- 二、创建 PathBuf
- 三、PathBuf 和 Path 的区别
- 四、使用 push() 拼接路径
- 五、使用 join() 拼接路径
- 六、使用 pop() 删除最后一级路径
- 七、读取路径中的各个部分
- 八、修改文件名
- 九、修改扩展名
- 十、追加扩展名:add_extension()
- 十一、判断绝对路径和相对路径
- 十二、检查文件或目录是否存在
- 十三、把路径转换成字符串
- 十四、PathBuf 转换成 &Path
- 十五、遍历路径组成部分
- 十六、获取规范化绝对路径
- 十七、配合 std::fs 操作文件
- 十八、函数参数推荐使用 AsRef<Path>
- 十九、实战:构建用户头像保存路径
- 二十、实战:修改上传文件的扩展名
- 二十一、实战:只允许指定扩展名
- 二十二、实战:防止简单的路径穿越
- 二十三、常见错误
- 二十四、常用方法汇总
- 二十五、核心理解
Rust PathBuf 使用详解
PathBuf 是 Rust 标准库中用于表示和操作文件路径、目录路径的类型。
use std::path::PathBuf;
可以把它简单理解为:
PathBuf就像路径专用的String,拥有路径数据,并且可以修改。
Rust 标准库同时提供了 Path 和 PathBuf:
| 字符串类型 | 路径类型 | 特点 |
|---|---|---|
String |
PathBuf |
拥有数据、可修改 |
&str |
&Path |
借用数据、通常只读 |
官方将 PathBuf 定义为“拥有所有权且可修改的路径”,并且它实现了到 Path 的解引用,因此 Path 上的大多数检查方法也能直接用于 PathBuf。(Rust 文档)
一、为什么不直接使用 String
路径表面上看起来是字符串:
C:\Users\Albert\config.toml
或者:
/home/albert/config.toml
但路径具有一些普通字符串没有的特性:
- Windows 和 Linux/macOS 的路径分隔符不同;
- 路径可能不是合法的 UTF-8;
- 路径包含父目录、文件名、扩展名、根目录等结构;
- 路径需要进行拼接、拆分、替换文件名等操作。
Rust 的 PathBuf 和 Path 是对 OsString 和 OsStr 的封装,可以按照当前操作系统的路径规则处理数据,而不是假设路径一定是 UTF-8 字符串。(Rust 文档)
例如,不建议这样手动拼接:
let base = "data";
let file = "config.toml";
let path = format!("{}/{}", base, file);
因为 Windows 常用 \,Unix 系统使用 /。
更推荐:
use std::path::PathBuf;
let mut path = PathBuf::from("data");
path.push("config.toml");
println!("{}", path.display());
在不同操作系统上,Rust 会按照对应平台的路径规则处理分隔符。
二、创建 PathBuf
1. 使用 PathBuf::new()
创建一个空路径:
use std::path::PathBuf;
fn main() {
let path = PathBuf::new();
println!("{:?}", path);
}
此时 path 是空路径。
通常配合 push() 动态构建路径:
use std::path::PathBuf;
fn main() {
let mut path = PathBuf::new();
path.push("data");
path.push("users");
path.push("user.json");
println!("{}", path.display());
}
Linux/macOS 上可能输出:
data/users/user.json
Windows 上可能输出:
data\users\user.json
2. 使用 PathBuf::from()
这是最常见的创建方式:
use std::path::PathBuf;
fn main() {
let path = PathBuf::from("config/app.toml");
println!("{}", path.display());
}
也可以从 String 创建:
use std::path::PathBuf;
fn main() {
let path_string = String::from("config/app.toml");
let path = PathBuf::from(path_string);
println!("{}", path.display());
}
这里 path_string 的所有权会被移动给 PathBuf。
3. Windows 路径建议使用原始字符串
Windows 路径中包含大量反斜杠:
C:\Users\Albert\Documents
普通字符串需要转义:
let path = PathBuf::from("C:\\Users\\Albert\\Documents");
更推荐使用原始字符串:
use std::path::PathBuf;
fn main() {
let path = PathBuf::from(r"C:\Users\Albert\Documents");
println!("{}", path.display());
}
r"..." 中的反斜杠不需要转义。
三、PathBuf 和 Path 的区别
PathBuf
拥有路径数据,可以修改:
use std::path::PathBuf;
let mut path = PathBuf::from("data");
path.push("config.toml");
Path
通常以 &Path 形式出现,只是借用路径:
use std::path::Path;
let path: &Path = Path::new("data/config.toml");
可以类比:
PathBuf 对应 String &Path 对应 &str
例如,下面这个函数只需要读取路径,不需要拥有或修改它,因此参数使用 &Path:
use std::path::Path;
fn print_path(path: &Path) {
println!("{}", path.display());
}
fn main() {
let path = Path::new("data/config.toml");
print_path(path);
}
而需要构建并返回路径时,可以返回 PathBuf:
use std::path::PathBuf;
fn build_config_path() -> PathBuf {
let mut path = PathBuf::from("config");
path.push("app.toml");
path
}
fn main() {
let path = build_config_path();
println!("{}", path.display());
}
四、使用 push() 拼接路径
push() 会修改原来的 PathBuf。
use std::path::PathBuf;
fn main() {
let mut path = PathBuf::from("data");
path.push("users");
path.push("10001");
path.push("profile.json");
println!("{}", path.display());
}
结果类似:
data/users/10001/profile.json
push() 的参数支持字符串、String、Path 和 PathBuf 等,因为它接收实现了 AsRef<Path> 的类型。
注意:追加绝对路径会覆盖原路径
use std::path::PathBuf;
fn main() {
let mut path = PathBuf::from("/home/albert");
path.push("/etc/config.toml");
println!("{}", path.display());
}
结果不是:
/home/albert/etc/config.toml
而是:
/etc/config.toml
因为追加的路径是绝对路径时,push() 会替换原路径;join() 也具有类似行为。(Rust 文档)
这个行为在接收用户输入时尤其要注意:
fn build_upload_path(file_name: &str) -> PathBuf {
let mut path = PathBuf::from("/data/uploads");
path.push(file_name);
path
}
如果 file_name 是:
/etc/passwd
原来的 /data/uploads 会被替换。
因此,不应该直接把不可信的完整路径传给 push()。
五、使用 join() 拼接路径
join() 与 push() 都可以拼接路径,但区别是:
push()修改原路径;join()返回一个新的PathBuf,不修改原路径。
push() 示例
use std::path::PathBuf;
fn main() {
let mut base = PathBuf::from("data");
base.push("config.toml");
println!("{}", base.display());
}
base 被修改为:
data/config.toml
join() 示例
use std::path::PathBuf;
fn main() {
let base = PathBuf::from("data");
let config_path = base.join("config.toml");
println!("base: {}", base.display());
println!("config: {}", config_path.display());
}
输出:
base: data config: data/config.toml
原来的 base 没有改变。
连续使用 join()
use std::path::PathBuf;
fn main() {
let path = PathBuf::from("data")
.join("users")
.join("10001")
.join("profile.json");
println!("{}", path.display());
}
Path::join() 会创建新的 PathBuf;当被拼接路径是绝对路径时,它也会替换前面的路径。(Rust 文档)
六、使用 pop() 删除最后一级路径
pop() 类似于返回上一级目录。
use std::path::PathBuf;
fn main() {
let mut path = PathBuf::from("data/users/10001/profile.json");
path.pop();
println!("{}", path.display());
}
输出:
data/users/10001
再次调用:
path.pop();
结果:
data/users
pop() 返回 bool:
- 成功删除最后一级,返回
true; - 已经无法继续删除,返回
false。(Rust 文档)
完整示例:
use std::path::PathBuf;
fn main() {
let mut path = PathBuf::from("data/users/profile.json");
while path.pop() {
println!("{}", path.display());
}
}
可能输出:
data/users data
七、读取路径中的各个部分
由于 PathBuf 可以使用 Path 的方法,因此可以直接调用:
parent()file_name()file_stem()extension()is_absolute()is_relative()
1. 获取父目录:parent()
use std::path::PathBuf;
fn main() {
let path = PathBuf::from("data/users/profile.json");
match path.parent() {
Some(parent) => {
println!("父目录:{}", parent.display());
}
None => {
println!("没有父目录");
}
}
}
输出:
父目录:data/users
parent() 返回:
Option<&Path>
因为某些路径可能不存在父路径,例如根目录。(Rust 文档)
2. 获取文件名:file_name()
use std::path::PathBuf;
fn main() {
let path = PathBuf::from("data/users/profile.json");
if let Some(file_name) = path.file_name() {
println!("文件名:{}", file_name.to_string_lossy());
}
}
输出:
文件名:profile.json
file_name() 返回:
Option<&OsStr>
不是 Option<&str>,因为操作系统路径不一定是合法 UTF-8。
3. 获取不含扩展名的文件名:file_stem()
use std::path::PathBuf;
fn main() {
let path = PathBuf::from("data/archive.tar.gz");
if let Some(stem) = path.file_stem() {
println!("文件主体:{}", stem.to_string_lossy());
}
}
输出:
文件主体:archive.tar
注意:
archive.tar.gz
最后一个扩展名是:
gz
因此 file_stem() 是:
archive.tar
官方文档中同样说明,file_stem() 取最后一个点之前的部分。(Rust 文档)
4. 获取扩展名:extension()
use std::path::PathBuf;
fn main() {
let path = PathBuf::from("data/archive.tar.gz");
match path.extension() {
Some(extension) => {
println!("扩展名:{}", extension.to_string_lossy());
}
None => {
println!("没有扩展名");
}
}
}
输出:
扩展名:gz
另一个例子:
let path = PathBuf::from("main.rs");
结果:
rs
5. 一次性获取完整信息
use std::path::PathBuf;
fn main() {
let path = PathBuf::from("data/reports/2026/report.pdf");
println!("完整路径:{}", path.display());
if let Some(parent) = path.parent() {
println!("父目录:{}", parent.display());
}
if let Some(file_name) = path.file_name() {
println!("文件名:{}", file_name.to_string_lossy());
}
if let Some(file_stem) = path.file_stem() {
println!("文件主体:{}", file_stem.to_string_lossy());
}
if let Some(extension) = path.extension() {
println!("扩展名:{}", extension.to_string_lossy());
}
}
输出:
完整路径:data/reports/2026/report.pdf 父目录:data/reports/2026 文件名:report.pdf 文件主体:report 扩展名:pdf
八、修改文件名
使用 set_file_name():
use std::path::PathBuf;
fn main() {
let mut path = PathBuf::from("data/config/app.toml");
path.set_file_name("system.toml");
println!("{}", path.display());
}
输出:
data/config/system.toml
父目录保持不变,只替换最后一级路径。
如果路径没有文件名,set_file_name() 的效果类似于 push()。(Rust 文档)
九、修改扩展名
使用 set_extension():
use std::path::PathBuf;
fn main() {
let mut path = PathBuf::from("data/report.txt");
path.set_extension("pdf");
println!("{}", path.display());
}
输出:
data/report.pdf
原来没有扩展名
let mut path = PathBuf::from("data/report");
path.set_extension("pdf");
结果:
data/report.pdf
删除扩展名
传入空字符串:
use std::path::PathBuf;
fn main() {
let mut path = PathBuf::from("data/report.pdf");
path.set_extension("");
println!("{}", path.display());
}
输出:
data/report
set_extension() 会替换最后一个扩展名,传入空字符串则删除扩展名;如果路径没有文件名,它返回 false。(Rust 文档)
tar.gz 的情况
use std::path::PathBuf;
fn main() {
let mut path = PathBuf::from("backup.tar.gz");
path.set_extension("zip");
println!("{}", path.display());
}
输出:
backup.tar.zip
因为 set_extension() 只替换最后一个扩展名。
十、追加扩展名:add_extension()
在较新的 Rust 版本中,可以使用 add_extension():
use std::path::PathBuf;
fn main() {
let mut path = PathBuf::from("backup.tar");
path.add_extension("gz");
println!("{}", path.display());
}
结果:
backup.tar.gz
区别:
let mut path = PathBuf::from("backup.tar");
path.set_extension("gz");
// backup.gz
而:
let mut path = PathBuf::from("backup.tar");
path.add_extension("gz");
// backup.tar.gz
add_extension() 在当前标准库中用于追加扩展名,而不是替换已有扩展名。(Rust 文档)
十一、判断绝对路径和相对路径
is_absolute()
use std::path::PathBuf;
fn main() {
let path = PathBuf::from("/home/albert/config.toml");
println!("{}", path.is_absolute());
}
Linux/macOS 上输出:
true
is_relative()
use std::path::PathBuf;
fn main() {
let path = PathBuf::from("config/app.toml");
println!("{}", path.is_relative());
}
输出:
true
Windows 上需要注意:
C:\Windows
是绝对路径,而:
C:Windows
不是完整的绝对路径。Rust 官方文档明确区分了 Windows 的驱动器前缀和根目录。(Rust 文档)
十二、检查文件或目录是否存在
1. exists()
use std::path::PathBuf;
fn main() {
let path = PathBuf::from("Cargo.toml");
if path.exists() {
println!("路径存在");
} else {
println!("路径不存在");
}
}
2. is_file()
if path.is_file() {
println!("这是普通文件");
}
3. is_dir()
if path.is_dir() {
println!("这是目录");
}
需要注意,exists()、is_file() 和 is_dir() 会把部分文件系统错误压缩成 false。需要区分“不存在”和“无权限访问”时,可以使用 try_exists() 或 std::fs::metadata()。(Rust 文档)
例如:
use std::io;
use std::path::Path;
fn check_path(path: &Path) -> io::Result<()> {
match path.try_exists()? {
true => println!("路径存在"),
false => println!("路径不存在"),
}
Ok(())
}
十三、把路径转换成字符串
1. 推荐用于打印:display()
use std::path::PathBuf;
fn main() {
let path = PathBuf::from("data/config.toml");
println!("{}", path.display());
}
display() 返回一个可以通过 {} 输出的辅助对象,适合日志和提示信息。路径可能包含非 Unicode 内容,因此这种显示在部分平台上可能采用有损转换。(Rust 文档)
2. 使用 to_str()
use std::path::PathBuf;
fn main() {
let path = PathBuf::from("data/config.toml");
match path.to_str() {
Some(value) => println!("{value}"),
None => println!("路径不是合法的 UTF-8"),
}
}
返回值是:
Option<&str>
因为路径可能不是合法 UTF-8。(Rust 文档)
3. 使用 to_string_lossy()
use std::path::PathBuf;
fn main() {
let path = PathBuf::from("data/config.toml");
let text = path.to_string_lossy();
println!("{text}");
}
返回类型:
Cow<'_, str>
如果路径包含无效 UTF-8 字节,会使用替换字符 � 代替。(Rust 文档)
不建议直接 unwrap()
不推荐:
let text = path.to_str().unwrap();
更安全:
if let Some(text) = path.to_str() {
println!("{text}");
}
或者仅用于显示:
println!("{}", path.display());
十四、PathBuf 转换成 &Path
使用 as_path()
use std::path::{Path, PathBuf};
fn print_path(path: &Path) {
println!("{}", path.display());
}
fn main() {
let path_buf = PathBuf::from("data/config.toml");
print_path(path_buf.as_path());
}
不过由于 Rust 会自动解引用,通常可以直接写:
print_path(&path_buf);
PathBuf::as_path() 会把 PathBuf 借用为 &Path,不会复制路径。(Rust 文档)
十五、遍历路径组成部分
使用 components():
use std::path::PathBuf;
fn main() {
let path = PathBuf::from("/home/albert/project/src/main.rs");
for component in path.components() {
println!("{component:?}");
}
}
在 Unix 系统上可能输出:
RootDir
Normal("home")
Normal("albert")
Normal("project")
Normal("src")
Normal("main.rs")
路径组件可能包括:
Component::Prefix(...) Component::RootDir Component::CurDir Component::ParentDir Component::Normal(...)
components() 会忽略重复分隔符、非开头的 . 和末尾分隔符,但不会自动解析普通路径中的 ..,也不会解析符号链接。(Rust 文档)
例如:
use std::path::Path;
fn main() {
let path = Path::new("data/./users/../config.toml");
for component in path.components() {
println!("{component:?}");
}
}
.. 仍会作为 ParentDir 存在。
十六、获取规范化绝对路径
可以使用 canonicalize():
use std::io;
use std::path::PathBuf;
fn main() -> io::Result<()> {
let path = PathBuf::from("./src/../Cargo.toml");
let absolute_path = path.canonicalize()?;
println!("{}", absolute_path.display());
Ok(())
}
它会:
- 返回绝对路径;
- 解析
.和..; - 解析符号链接;
- 实际访问文件系统。
因此,目标路径通常必须存在,否则会返回错误。(Rust 文档)
不要误以为:
PathBuf::from("a/b/../c")
创建时就会自动变成:
a/c
PathBuf 默认不会进行这种完整规范化。
十七、配合 std::fs 操作文件
PathBuf 只负责表示路径,本身不会自动创建文件或目录。
示例一:创建目录
use std::fs;
use std::io;
use std::path::PathBuf;
fn main() -> io::Result<()> {
let path = PathBuf::from("data")
.join("uploads")
.join("images");
fs::create_dir_all(&path)?;
println!("目录创建成功:{}", path.display());
Ok(())
}
最终创建:
data/uploads/images
示例二:写入文件
use std::fs;
use std::io;
use std::path::PathBuf;
fn main() -> io::Result<()> {
let directory = PathBuf::from("data");
fs::create_dir_all(&directory)?;
let file_path = directory.join("config.toml");
fs::write(&file_path, "port = 8080\n")?;
println!("文件已写入:{}", file_path.display());
Ok(())
}
示例三:读取文件
use std::fs;
use std::io;
use std::path::PathBuf;
fn main() -> io::Result<()> {
let file_path = PathBuf::from("data").join("config.toml");
let content = fs::read_to_string(&file_path)?;
println!("文件路径:{}", file_path.display());
println!("文件内容:\n{content}");
Ok(())
}
十八、函数参数推荐使用 AsRef<Path>
如果一个函数需要接收路径,可以这样写:
use std::path::Path;
fn print_file_name<P: AsRef<Path>>(path: P) {
let path = path.as_ref();
if let Some(file_name) = path.file_name() {
println!("{}", file_name.to_string_lossy());
}
}
fn main() {
print_file_name("data/config.toml");
print_file_name(String::from("data/user.json"));
}
还可以传入 PathBuf:
use std::path::PathBuf;
let path = PathBuf::from("data/report.pdf");
print_file_name(path);
这种写法的优点是调用者可以传入:
&strString&PathPathBuf&PathBuf
例如,一个通用文件读取函数:
use std::fs;
use std::io;
use std::path::Path;
fn read_text<P: AsRef<Path>>(path: P) -> io::Result<String> {
fs::read_to_string(path)
}
fn main() -> io::Result<()> {
let content = read_text("Cargo.toml")?;
println!("{content}");
Ok(())
}
十九、实战:构建用户头像保存路径
假设需要生成:
data/uploads/users/10001/avatar.png
可以这样写:
use std::path::{Path, PathBuf};
fn build_avatar_path(base_dir: &Path, user_id: u64) -> PathBuf {
base_dir
.join("uploads")
.join("users")
.join(user_id.to_string())
.join("avatar.png")
}
fn main() {
let base_dir = Path::new("data");
let avatar_path = build_avatar_path(base_dir, 10001);
println!("{}", avatar_path.display());
}
输出:
data/uploads/users/10001/avatar.png
这里的设计方式比较符合 Rust 的习惯:
- 输入参数借用
&Path; - 函数内部生成新路径;
- 返回拥有所有权的
PathBuf。
二十、实战:修改上传文件的扩展名
use std::path::{Path, PathBuf};
fn change_to_backup_file(path: &Path) -> PathBuf {
let mut result = path.to_path_buf();
result.set_extension("bak");
result
}
fn main() {
let source = Path::new("data/config.toml");
let backup = change_to_backup_file(source);
println!("原文件:{}", source.display());
println!("备份文件:{}", backup.display());
}
输出:
原文件:data/config.toml 备份文件:data/config.bak
to_path_buf() 类似于:
&str.to_string()
它会从借用的 &Path 创建一个拥有所有权的 PathBuf。(Rust 文档)
二十一、实战:只允许指定扩展名
use std::path::Path;
fn is_allowed_file(path: &Path) -> bool {
matches!(
path.extension().and_then(|ext| ext.to_str()),
Some("jpg" | "jpeg" | "png" | "gif")
)
}
fn main() {
let paths = [
Path::new("avatar.png"),
Path::new("photo.jpg"),
Path::new("document.pdf"),
Path::new("README"),
];
for path in paths {
println!(
"{}:{}",
path.display(),
if is_allowed_file(path) {
"允许"
} else {
"不允许"
}
);
}
}
输出:
avatar.png:允许 photo.jpg:允许 document.pdf:不允许 README:不允许
需要忽略大小写时:
use std::path::Path;
fn is_allowed_file(path: &Path) -> bool {
path.extension()
.and_then(|ext| ext.to_str())
.map(|ext| {
matches!(
ext.to_ascii_lowercase().as_str(),
"jpg" | "jpeg" | "png" | "gif"
)
})
.unwrap_or(false)
}
不过扩展名只能作为辅助判断,不能可靠证明文件的真实内容类型。
二十二、实战:防止简单的路径穿越
假设用户上传文件名:
../../etc/passwd
不应该直接这样做:
let path = PathBuf::from("uploads").join(user_input);
可以先要求输入只能包含一个普通文件名:
use std::path::{Component, Path, PathBuf};
fn safe_upload_path(base: &Path, file_name: &str) -> Option<PathBuf> {
let input = Path::new(file_name);
let mut components = input.components();
match (components.next(), components.next()) {
(Some(Component::Normal(name)), None) => Some(base.join(name)),
_ => None,
}
}
fn main() {
let base = Path::new("uploads");
let normal = safe_upload_path(base, "avatar.png");
let dangerous = safe_upload_path(base, "../../etc/passwd");
println!("正常路径:{normal:?}");
println!("危险路径:{dangerous:?}");
}
输出类似:
正常路径:Some("uploads/avatar.png")
危险路径:None
这段代码只接受单个普通路径组件,不接受:
- 绝对路径;
..;- 子目录;
- 多级路径。
二十三、常见错误
错误一:使用字符串手动拼接
不推荐:
let path = format!("{}\\{}", base, file_name);
推荐:
let path = PathBuf::from(base).join(file_name);
错误二:直接把路径转换为 String
不推荐:
let value = path.to_str().unwrap().to_string();
打印时推荐:
println!("{}", path.display());
确实需要字符串时:
let value = path.to_string_lossy().into_owned();
错误三:误以为 join() 会修改原路径
use std::path::PathBuf;
fn main() {
let base = PathBuf::from("data");
base.join("config.toml");
println!("{}", base.display());
}
输出仍然是:
data
因为 join() 返回新的 PathBuf,需要接收结果:
let config_path = base.join("config.toml");
错误四:忽略绝对路径覆盖行为
let path = PathBuf::from("/data/uploads").join("/etc/passwd");
结果可能是:
/etc/passwd
而不是:
/data/uploads/etc/passwd
错误五:把 PathBuf 当成真实文件
let path = PathBuf::from("data/config.toml");
这只是创建了一个路径值,不会创建:
data目录;config.toml文件。
真正创建文件需要:
std::fs::write(&path, "内容")?;
二十四、常用方法汇总
| 方法 | 作用 |
|---|---|
PathBuf::new() |
创建空路径 |
PathBuf::from() |
从字符串等数据创建路径 |
push() |
在原路径后追加路径 |
join() |
返回拼接后的新路径 |
pop() |
删除最后一级路径 |
parent() |
获取父路径 |
file_name() |
获取文件名 |
file_stem() |
获取不含最后一个扩展名的文件名 |
extension() |
获取扩展名 |
set_file_name() |
修改文件名 |
set_extension() |
修改或删除扩展名 |
add_extension() |
追加扩展名 |
is_absolute() |
是否为绝对路径 |
is_relative() |
是否为相对路径 |
exists() |
路径是否存在 |
try_exists() |
检查路径是否存在并保留错误 |
is_file() |
是否为文件 |
is_dir() |
是否为目录 |
components() |
遍历路径组成部分 |
canonicalize() |
获取规范化绝对路径 |
display() |
用于输出路径 |
to_str() |
尝试转换为 &str |
to_string_lossy() |
有损转换为字符串 |
as_path() |
借用为 &Path |
to_path_buf() |
从 &Path 创建拥有所有权的 PathBuf |
二十五、核心理解
记住下面几点,基本就掌握了 PathBuf:
PathBuf ≈ 路径版本的 String &Path ≈ 路径版本的 &str
需要保存、构建、修改、返回路径时使用:
PathBuf
只需要读取、检查或作为函数参数时,优先使用:
&Path
动态修改原路径:
path.push("config.toml");
生成新路径:
let config_path = base.join("config.toml");
安全打印路径:
println!("{}", path.display());
推荐的函数签名通常是:
use std::path::{Path, PathBuf};
fn build_path(base: &Path) -> PathBuf {
base.join("config.toml")
}