标准库也提供了很多其他类型来支持某些功能,例如:
- 线程(Threads)
- 信道(Channels)
- 文件输入输出(File I/O)
这些内容在原生类型之外进行了有效扩充。
参见:
20.1 线程
Rust 通过 spawn 函数提供了创建本地操作系统(native OS)线程的机制,该函数的参数是一个通过值捕获变量的闭包(moving closure)。
// 20.1节 线程
use std::thread;
static NTHREADS: i32 = 10;
// 这是主(`main`)线程
fn main() {
// 提供一个 vector 来存放所创建的子线程(children)。
let mut children = vec![];
for i in 0..NTHREADS {
// 启动(spin up)另一个线程
children.push(thread::spawn(move || {
//打印线程号
println!("线程 {} 的 ID: {:?}", i, thread::current().id());
println!("this is thread number {}", i)
}));
}
for child in children {
// 等待线程结束。返回一个结果。
let _ = child.join();
}
println!("Hello Rust");
}
// rustc main.rs
// ./main
编译运行:
PS F:\rustproject\rustbyexample\chapter20\example20_1> rustc main.rs
PS F:\rustproject\rustbyexample\chapter20\example20_1> ./main
线程 2 的 ID: ThreadId(4)
线程 3 的 ID: ThreadId(5)
this is thread number 3
线程 5 的 ID: ThreadId(7)
this is thread number 5
this is thread number 2
线程 7 的 ID: ThreadId(9)
this is thread number 7
线程 9 的 ID: ThreadId(11)
this is thread number 9
线程 1 的 ID: ThreadId(3)
线程 0 的 ID: ThreadId(2)
this is thread number 0
线程 8 的 ID: ThreadId(10)
this is thread number 8
this is thread number 1
线程 6 的 ID: ThreadId(8)
this is thread number 6
线程 4 的 ID: ThreadId(6)
this is thread number 4
Hello Rust
PS F:\rustproject\rustbyexample\chapter20\example20_1>

这些线程由操作系统调度(schedule)。
20.1.1 测试实例:map-reduce
Rust 使数据的并行化处理非常简单,在 Rust 中你无需面对并行处理的很多传统难题。
标准库提供了开箱即用的线程类型,把它和 Rust 的所有权概念与别名规则结合起来,可以自动地避免数据竞争(data race)。
当某状态对某线程是可见的,别名规则(即一个可变引用 XOR 一些只读引用。译注:XOR 是异或的意思,即「二者仅居其一」)就自动地避免了别的线程对它的操作。(当需要同步处理时,请使用 Mutex 或 Channel 这样的同步类型。)
在本例中,我们将会计算一堆数字中每一位的和。我们将把它们分成几块,放入不同的线程。每个线程会把自己那一块数字的每一位加起来,之后我们再把每个线程提供的结果再加起来。
注意到,虽然我们在线程之间传递了引用,但 Rust 理解我们是在传递只读的引用,因此不会发生数据竞争等不安全的事情。另外,因为我们把数据块 move 到了线程中,Rust 会保证数据存活至线程退出,因此不会产生悬挂指针。
// 20.1.1节 测试实例:map-reduce
use std::thread;
// 这是主(`main`)线程
fn main() {
// 这是我们要处理的数据。
// 我们会通过线程实现 map-reduce 算法,从而计算每一位的和
// 每个用空白符隔开的块都会分配给单独的线程来处理
//
// 试一试:插入空格,看看输出会怎样变化!
let data = "86967897737416471853297327050364959
11861322575564723963297542624962850
70856234701860851907960690014725639
38397966707106094172783238747669219
52380795257888236525459303330302837
58495327135744041048897885734297812
69920216438980873548808413720956532
16278424637452589860345374828574668";
let mut children = vec![];
/*************************************************************************
* "Map" 阶段
*
* 把数据分段,并进行初始化处理
************************************************************************/
// 把数据分段,每段将会单独计算
// 每段都是完整数据的一个引用(&str)
let chunked_data = data.split_whitespace();
// 对分段的数据进行迭代。
// .enumerate() 会把当前的迭代计数与被迭代的元素以元组 (index, element)
// 的形式返回。接着立即使用 "解构赋值" 将该元组解构成两个变量,
// `i` 和 `data_segment`。
for (i, data_segment) in chunked_data.enumerate() {
println!("data segment {} is \"{}\"", i, data_segment);
// 用单独的线程处理每一段数据
//
// spawn() 返回新线程的句柄(handle),我们必须拥有句柄,
// 才能获取线程的返回值。
//
// 'move || -> u32' 语法表示该闭包:
// * 没有参数('||')
// * 会获取所捕获变量的所有权('move')
// * 返回无符号 32 位整数('-> u32')
//
// Rust 可以根据闭包的内容推断出 '-> u32',所以我们可以不写它。
//
// 试一试:删除 'move',看看会发生什么
children.push(thread::spawn(move || -> u32 {
// 计算该段的每一位的和:
let result = data_segment
// 对该段中的字符进行迭代..
.chars()
// ..把字符转成数字..
.map(|c| c.to_digit(10).expect("should be a digit"))
// ..对返回的数字类型的迭代器求和
.sum();
// println! 会锁住标准输出,这样各线程打印的内容不会交错在一起
println!("processed segment {}, result = {}", i, result);
// 不需要 "return",因为 Rust 是一种 "表达式语言",每个代码块中
// 最后求值的表达式就是代码块的值。
result
}));
}
/*************************************************************************
* "Reduce" 阶段
*
* 收集中间结果,得出最终结果
************************************************************************/
// 把每个线程产生的中间结果收入一个新的向量中
let mut intermediate_sums = vec![];
for child in children {
// 收集每个子线程的返回值
let intermediate_sum = child.join().unwrap();
intermediate_sums.push(intermediate_sum);
}
// 把所有中间结果加起来,得到最终结果
//
// 我们用 "涡轮鱼" 写法 ::<> 来为 sum() 提供类型提示。
//
// 试一试:不使用涡轮鱼写法,而是显式地指定 intermediate_sums 的类型
let final_result = intermediate_sums.iter().sum::<u32>();
println!("Final sum result: {}", final_result);
println!("Hello Rust");
}
// rustc main.rs
// ./main
编译运行:
PS F:\rustproject\rustbyexample\chapter20\example20_2> rustc main.rs
PS F:\rustproject\rustbyexample\chapter20\example20_2> ./main
data segment 0 is "86967897737416471853297327050364959"
data segment 1 is "11861322575564723963297542624962850"
data segment 2 is "70856234701860851907960690014725639"
data segment 3 is "38397966707106094172783238747669219"
data segment 4 is "52380795257888236525459303330302837"
processed segment 0, result = 187
processed segment 2, result = 154
processed segment 1, result = 157
data segment 5 is "58495327135744041048897885734297812"
processed segment 3, result = 177
processed segment 4, result = 153
data segment 6 is "69920216438980873548808413720956532"
processed segment 5, result = 172
data segment 7 is "16278424637452589860345374828574668"
processed segment 6, result = 165
processed segment 7, result = 177
Final sum result: 1342
Hello Rust
PS F:\rustproject\rustbyexample\chapter20\example20_2>

作业
根据用户输入的数据来决定线程的数量是不明智的。如果用户输入的数据中有一大堆空格怎么办?我们真的想要创建 2000 个线程吗?
请修改程序,使得数据总是被分成有限数目的段,这个数目是由程序开头的静态常量决定的。
// 20.1.1节 测试实例:map-reduce (作业)
use std::thread;
// 静态常量,指定要分割成的段数
const NUM_SEGMENTS: usize = 6;
// 这是主(`main`)线程
fn main() {
// 这是我们要处理的数据。
let data = "86967897737416471853297327050364959
11861322575564723963297542624962850
70856234701860851907960690014725639
38397966707106094172783238747669219
52380795257888236525459303330302837
58495327135744041048897885734297812
69920216438980873548808413720956532
16278424637452589860345374828574668";
// 移除所有空白字符,得到连续的数字字符串
let digits: String = data.chars().filter(|c| !c.is_whitespace()).collect();
let total_len = digits.len();
if total_len == 0 {
println!("数据为空");
return;
}
if NUM_SEGMENTS == 0 {
println!("NUM_SEGMENTS 必须大于0");
return;
}
// 计算每段的基本长度和余数
let base_len = total_len / NUM_SEGMENTS;
let remainder = total_len % NUM_SEGMENTS;
let mut start = 0;
let mut children = vec![];
for i in 0..NUM_SEGMENTS {
// 前 remainder 段每段多分配一个字符
let end = start + base_len + if i < remainder {1} else {0};
let segment = &digits[start..end];
// 转换为 String 以便在线程中独立拥有数据
let segment_string = segment.to_string();
println!("data segment {} is \"{}\"", i, segment_string);
children.push(thread::spawn(move || -> u32 {
// 计算该段的每一位的和:
let result = segment_string
.chars()
.map(|c| c.to_digit(10).expect("should be a digit"))
.sum();
println!("processed segment {}, result = {}", i, result);
result
}));
start = end;
}
// 把每个线程产生的中间结果收入一个新的向量中
let mut intermediate_sums = vec![];
for child in children {
// 收集每个子线程的返回值
let intermediate_sum = child.join().unwrap();
intermediate_sums.push(intermediate_sum);
}
let final_result = intermediate_sums.iter().sum::<u32>();
println!("Final sum result: {}", final_result);
println!("Hello Rust");
}
// rustc main.rs
// ./main
编译运行:
PS F:\rustproject\rustbyexample\chapter20\example20_3> rustc main.rs
PS F:\rustproject\rustbyexample\chapter20\example20_3> ./main
data segment 0 is "86967897737416471853297327050364959118613225755"
data segment 1 is "64723963297542624962850708562347018608519079606"
processed segment 0, result = 233
data segment 2 is "90014725639383979667071060941727832387476692195"
processed segment 1, result = 219
data segment 3 is "23807952578882365254593033303028375849532713574"
processed segment 2, result = 228
data segment 4 is "4041048897885734297812699202164389808735488084"
processed segment 3, result = 211
processed segment 4, result = 231
data segment 5 is "1372095653216278424637452589860345374828574668"
processed segment 5, result = 220
Final sum result: 1342
Hello Rust
PS F:\rustproject\rustbyexample\chapter20\example20_3>

参见:
20.2 通道
Rust 为线程之间的通信提供了异步的通道(channel)。通道允许两个端点之间信息的单向流动:Sender(发送端) 和 Receiver(接收端)。
// 20.2节 通道
use std::sync::mpsc::{Sender, Receiver};
use std::sync::mpsc;
use std::thread;
static NTHREADS: i32 = 3;
fn main() {
// 通道有两个端点:`Sender<T>` 和 `Receiver<T>`,其中 `T` 是要发送
// 的消息的类型(类型标注是可选的)
let (tx, rx): (Sender<i32>, Receiver<i32>) = mpsc::channel();
for id in 0..NTHREADS {
// sender 端可被复制
let thread_tx = tx.clone();
// 每个线程都将通过通道来发送它的 id
thread::spawn(move || {
// 被创建的线程取得 `thread_tx` 的所有权
// 每个线程都把消息放在通道的消息队列中
thread_tx.send(id).unwrap();
// 发送是一个非阻塞(non-blocking)操作,线程将在发送完消息后
// 会立即继续进行
println!("thread {} finished", id);
});
}
// 所有消息都在此处被收集
let mut ids = Vec::with_capacity(NTHREADS as usize);
for _ in 0..NTHREADS {
// `recv` 方法从通道中拿到一个消息
// 若无可用消息的话,`recv` 将阻止当前线程
ids.push(rx.recv());
}
// 显示消息被发送的次序
println!("{:?}", ids);
println!("Hello Rust");
}
// rustc main.rs
// ./main
编译运行:
PS F:\rustproject\rustbyexample\chapter20\example20_4> rustc main.rs
PS F:\rustproject\rustbyexample\chapter20\example20_4> ./main
thread 2 finished
thread 1 finished
thread 0 finished
[Ok(2), Ok(1), Ok(0)]
Hello Rust
PS F:\rustproject\rustbyexample\chapter20\example20_4>

20.3 路径
Path 结构体代表了底层文件系统的文件路径。Path 分为两种:posix::Path,针对类 UNIX 系统;以及 windows::Path,针对 Windows。prelude 会选择并输出符合平台类型的 Path 种类。
译注:prelude 是 Rust 自动地在每个程序中导入的一些通用的东西,这样我们就不必每写 一个程序就手动导入一番。
Path 可从 OsStr 类型创建,并且它提供数种方法,用于获取路径指向的文件/目录的信息。
注意 Path 在内部并不是用 UTF-8 字符串表示的,而是存储为若干字节(Vec<u8>)的 vector。因此,将 Path 转化成 &str 并非零开销的(free),且可能失败(因此它返回一个 Option)。
// 20.3节 路径
use std::path::Path;
fn main() {
// 从 `&'static str` 创建一个 `Path`
let path = Path::new(".");
// `display` 方法返回一个可显示(showable)的结构体
let display = path.display();
println!("display = {:?}", display);
// `join` 使用操作系统特定的分隔符来合并路径到一个字节容器,并返回新的路径
let new_path = path.join("a").join("b");
// 将路径转换成一个字符串切片
match new_path.to_str() {
None => panic!("new path is not a valid UTF-8 sequence"),
Some(s) => println!("new path is {}", s),
}
println!("Hello Rust");
}
// rustc main.rs
// ./main
编译运行:
PS F:\rustproject\rustbyexample\chapter20\example20_5> rustc main.rs
PS F:\rustproject\rustbyexample\chapter20\example20_5> ./main
display = "."
new path is .\a\b
Hello Rust
PS F:\rustproject\rustbyexample\chapter20\example20_5>

记得看看其他的 Path 方法(posix::Path 或 windows::Path 的),还有 Metadata 结构体类型。
参见:
20.4 文件输入输出(I/O)
File 结构体表示一个被打开的文件(它包裹了一个文件描述符),并赋予了对所表示的文件的读写能力。
由于在进行文件 I/O(输入/输出)操作时可能出现各种错误,因此 File 的所有方法都返回 io::Result<T> 类型,它是 Result<T, io::Error> 的别名。
这使得所有 I/O 操作的失败都变成显式的。借助这点,程序员可以看到所有的失败路径,并被鼓励主动地处理这些情形。
20.4.1 打开文件open
open 静态方法能够以只读模式(read-only mode)打开一个文件。
File 拥有资源,即文件描述符(file descriptor),它会在自身被 drop 时关闭文件。
// 20.4.1节 打开文件 open
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
fn main() {
// 创建指向所需的文件的路径
let path = Path::new("hello.txt");
// `display` 方法返回一个可显示(showable)的结构体
let display = path.display();
println!("display = {:?}", display);
// 以只读方式打开路径,返回 `io::Result<File>`
let mut file = match File::open(&path) {
// `io::Error` 的 `description` 方法返回一个描述错误的字符串。
Err(why) => panic!("couldn't open {}: {:?}", display, why),
Ok(file) => file,
};
// 读取文件内容到一个字符串,返回 `io::Result<usize>`
let mut s = String::new();
match file.read_to_string(&mut s) {
Err(why) => panic!("couldn't read {}: {:?}", display, why),
Ok(_) => print!("{} contains:\n{} \n", display, s),
}
// `file` 离开作用域,并且 `hello.txt` 文件将被关闭。
println!("Hello Rust");
}
// rustc main.rs
// ./main
下面是所希望的成功的输出:
PS F:\rustproject\rustbyexample\chapter20\example20_6> rustc main.rs
PS F:\rustproject\rustbyexample\chapter20\example20_6> ./main
display = "hello.txt"
hello.txt contains:
aaaa
Hello Rust
PS F:\rustproject\rustbyexample\chapter20\example20_6>

文件不存在

(我们鼓励您在不同的失败条件下测试前面的例子:hello.txt 不存在,或 hello.txt 不可读,等等。)
20.4.2 创建文件create
create 静态方法以只写模式(write-only mode)打开一个文件。若文件已经存在,则旧内容将被销毁。否则,将创建一个新文件。
// 20.4.2节 创建文件 create
static LOREM_IPSUM: &'static str =
"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
";
use std::io::prelude::*;
use std::fs::File;
use std::path::Path;
fn main() {
let path = Path::new("lorem_ipsum.txt");
// `display` 方法返回一个可显示(showable)的结构体
let display = path.display();
println!("display = {:?}", display);
// 以只写模式打开文件,返回 `io::Result<File>`
let mut file = match File::create(&path) {
Err(why) => panic!("couldn't create {}: {:?}", display, why),
Ok(file) => file,
};
// 将 `LOREM_IPSUM` 字符串写进 `file`,返回 `io::Result<()>`
match file.write_all(LOREM_IPSUM.as_bytes()) {
Err(why) => {
panic!("couldn't write to {}: {:?}", display, why)
},
Ok(_) => println!("successfully wrote to {}", display),
}
// `file` 离开作用域,并且 `hello.txt` 文件将被关闭。
println!("Hello Rust");
}
// rustc main.rs
// ./main
下面是预期成功的输出:
$ mkdir out
$ rustc create.rs && ./create
successfully wrote to out/lorem_ipsum.txt
=======================================================
PS F:\rustproject\rustbyexample\chapter20\example20_7> rustc main.rs
PS F:\rustproject\rustbyexample\chapter20\example20_7> ./main
display = "lorem_ipsum.txt"
successfully wrote to lorem_ipsum.txt
Hello Rust
PS F:\rustproject\rustbyexample\chapter20\example20_7> cat .\lorem_ipsum.txt
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
PS F:\rustproject\rustbyexample\chapter20\example20_7>

(和前面例子一样,我们鼓励你在失败条件下测试这个例子。)
还有一个更通用的 open_mode 方法,这能够以其他方式来来打开文件,如:read+write(读 + 写),追加(append),等等。
20.4.3 读取行read lines
方法 lines() 在文件的行上返回一个迭代器。
File::open 需要一个泛型 AsRef<Path>。这正是 read_lines() 期望的输入。
// 20.4.3节 读取行
use std::fs::File;
use std::io::{self, BufRead};
use std::path::Path;
fn main() {
// 在生成输出之前,文件主机必须存在于当前路径中
if let Ok(lines) = read_lines("./hosts") {
// 使用迭代器,返回一个(可选)字符串
for line in lines {
if let Ok(ip) = line {
println!("{}", ip);
}
}
}
println!("Hello Rust");
}
// 输出包裹在 Result 中以允许匹配错误,
// 将迭代器返回给文件行的读取器(Reader)。
fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
where P: AsRef<Path>, {
let file = File::open(filename)?;
Ok(io::BufReader::new(file).lines())
}
// rustc main.rs
// ./main
运行此程序将一行行将内容打印出来。
PS F:\rustproject\rustbyexample\chapter20\example20_8> echo "127.0.0.1\n192.168.0.1\n" > hosts
PS F:\rustproject\rustbyexample\chapter20\example20_8> rustc main.rs
PS F:\rustproject\rustbyexample\chapter20\example20_8> ./main
192.168.0.1
Hello Rust
PS F:\rustproject\rustbyexample\chapter20\example20_8>

这个过程比在内存中创建 String 更有效,特别是处理更大的文件。
20.5 子进程
process::Output 结构体表示已结束的子进程(child process)的输出,而 process::Command 结构体是一个进程创建者(process builder)。
// 20.5节 子进程
use std::process::Command;
fn main() {
let output = Command::new("rustc")
.arg("--version")
.output().unwrap_or_else(|e| {
panic!("failed to execute process: {}", e)
});
if output.status.success() {
let s = String::from_utf8_lossy(&output.stdout);
print!("rustc succeeded and stdout was:\n{}", s);
} else {
let s = String::from_utf8_lossy(&output.stderr);
print!("rustc failed and stderr was:\n{}", s);
}
println!("Hello Rust");
}
// rustc main.rs
// ./main
编译运行:
PS F:\rustproject\rustbyexample\chapter20\example20_9> rustc main.rs
PS F:\rustproject\rustbyexample\chapter20\example20_9> ./main
rustc succeeded and stdout was:
rustc 1.92.0 (ded5c06cf 2025-12-08)
Hello Rust
PS F:\rustproject\rustbyexample\chapter20\example20_9>

(再试试上面的例子,给 rustc 命令传入一个错误的 flag)
20.5.1 管道
std::Child 结构体代表了一个正在运行的子进程,它暴露了 stdin(标准输入),stdout(标准输出)和 stderr(标准错误)句柄,从而可以通过管道与所代表的进程交互。
// 20.5.1节 管道
use std::io::prelude::*;
use std::process::{Command, Stdio};
static PANGRAM: &'static str =
"the quick brown fox jumped over the lazy dog\n";
fn main() {
// 启动 `wc` 命令
let process = match Command::new("wc")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn() {
Err(why) => panic!("couldn't spawn wc: {:?}", why),
Ok(process) => process,
};
// 将字符串写入 `wc` 的 `stdin`。
//
// `stdin` 拥有 `Option<ChildStdin>` 类型,不过我们已经知道这个实例不为空值,
// 因而可以直接 `unwrap 它。
match process.stdin.unwrap().write_all(PANGRAM.as_bytes()) {
Err(why) => panic!("couldn't write to wc stdin: {:?}", why),
Ok(_) => println!("sent pangram to wc"),
}
// 因为 `stdin` 在上面调用后就不再存活,所以它被 `drop` 了,管道也被关闭。
// 这点非常重要,因为否则 `wc` 就不会开始处理我们刚刚发送的输入。
// `stdout` 字段也拥有 `Option<ChildStdout>` 类型,所以必需解包。
let mut s = String::new();
match process.stdout.unwrap().read_to_string(&mut s) {
Err(why) => panic!("couldn't read wc stdout: {:?}", why),
Ok(_) => print!("wcc responded with:\n{}", s),
}
println!("Hello Rust");
}
// rustc main.rs
// ./main
编译运行:(ubuntu2004环境)
scott@ubuntu2004:~/trunk/rustbyexample/chapter20$ rustc main.rs
scott@ubuntu2004:~/trunk/rustbyexample/chapter20$ ./main
sent pangram to wc
wcc responded with:
1 9 45
Hello Rust
scott@ubuntu2004:~/trunk/rustbyexample/chapter20$

20.5.2 等待
如果你想等待一个 process::Child 完成,就必须调用 Child::wait,这会返回一个 process::ExitStatus。
// 20.5.2节 等待
use std::process::Command;
fn main() {
let mut child = Command::new("sleep").arg("5").spawn().unwrap();
let _result = child.wait().unwrap();
println!("result = {:?}", _result);
println!("reached end of main");
println!("Hello Rust");
}
// rustc main.rs
// ./main
编译运行:(ubuntu2004环境)
scott@ubuntu2004:~/trunk/rustbyexample/chapter20$ rustc main.rs
scott@ubuntu2004:~/trunk/rustbyexample/chapter20$ ./main
result = ExitStatus(unix_wait_status(0))
reached end of main
Hello Rust
scott@ubuntu2004:~/trunk/rustbyexample/chapter20$

20.6 文件系统操作
std::io::fs 模块包含几个处理文件系统的函数。
// 20.6节 文件系统操作
use std::fs;
use std::fs::{File, OpenOptions};
use std::io;
use std::io::prelude::*;
use std::os::unix;
use std::path::Path;
// `% cat path` 的简单实现
fn cat(path: &Path) -> io::Result<String> {
let mut f = File::open(path)?;
let mut s = String::new();
match f.read_to_string(&mut s) {
Ok(_) => Ok(s),
Err(e) => Err(e),
}
}
// `% echo s > path` 的简单实现
fn echo(s: &str, path: &Path) -> io::Result<()> {
let mut f = File::create(path)?;
f.write_all(s.as_bytes())
}
// `% touch path` 的简单实现(忽略已存在的文件)
fn touch(path: &Path) -> io::Result<()> {
match OpenOptions::new().create(true).write(true).open(path) {
Ok(_) => Ok(()),
Err(e) => Err(e),
}
}
fn main() {
println!("'mkdir a'");
// 创建一个目录,返回 `io::Result<()>`
match fs::create_dir("a") {
Err(why) => println!("! {:?}", why.kind()),
Ok(_) => {},
}
println!("'echo hello > a/b.txt'");
// 前面的匹配可以用 `unwrap_or_else` 方法简化
echo("hello", &Path::new("a/b.txt")).unwrap_or_else(|why| {
println!("! {:?}", why.kind());
});
println!("'mkdir -p a/c/d'");
// 递归地创建一个目录,返回 `io::Result<()>`
fs::create_dir_all("a/c/d").unwrap_or_else(|why| {
println!("! {:?}", why.kind());
});
println!("'touch a/c/e.txt'");
touch(&Path::new("a/c/e.txt")).unwrap_or_else(|why| {
println!("! {:?}", why.kind());
});
println!("'ln -s ../b.txt a/c/b.txt'");
// 创建一个符号链接,返回 `io::Resutl<()>`
if cfg!(target_family = "unix") {
unix::fs::symlink("../b.txt", "a/c/b.txt").unwrap_or_else(|why| {
println!("! {:?}", why.kind());
});
}
println!("'cat a/c/b.txt'");
match cat(&Path::new("a/c/b.txt")) {
Err(why) => println!("! {:?}", why.kind()),
Ok(s) => println!("> {}", s),
}
println!("'ls a'");
// 读取目录的内容,返回 `io::Result<Vec<Path>>`
match fs::read_dir("a") {
Err(why) => println!("! {:?}", why.kind()),
Ok(paths) => for path in paths {
println!("> {:?}", path.unwrap().path());
},
}
println!("'rm a/c/e.txt'");
// 删除一个文件,返回 `io::Result<()>`
fs::remove_file("a/c/e.txt").unwrap_or_else(|why| {
println!("! {:?}", why.kind());
});
println!("'rmdir a/c/d'");
// 移除一个空目录,返回 `io::Result<()>`
fs::remove_dir("a/c/d").unwrap_or_else(|why| {
println!("! {:?}", why.kind());
});
println!("Hello Rust");
}
// rustc fs.rs
// ./fs
下面是所期望的成功的输出:(ubuntu2004编译环境)
scott@ubuntu2004:~/trunk/rustbyexample/chapter20$ rustc fs.rs
scott@ubuntu2004:~/trunk/rustbyexample/chapter20$ ./fs
'mkdir a'
'echo hello > a/b.txt'
'mkdir -p a/c/d'
'touch a/c/e.txt'
'ln -s ../b.txt a/c/b.txt'
'cat a/c/b.txt'
> hello
'ls a'
> "a/b.txt"
> "a/c"
'rm a/c/e.txt'
'rmdir a/c/d'
Hello Rust
scott@ubuntu2004:~/trunk/rustbyexample/chapter20$

且 a 目录的最终状态为:
$ tree a
a
|-- b.txt
`-- c
`-- b.txt -> ../b.txt
1 directory, 2 files
另一种定义 cat 函数的方式是使用 ? 标记:
fn cat(path: &Path) -> io::Result<String> {
let mut f = File::open(path)?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}
参见:
20.7 程序参数
标准库
命令行参数可使用 std::env::args 进行接收,这将返回一个迭代器,该迭代器会对每个参数举出一个字符串。
// 20.7节 程序参数
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
// 第一个参数是调用本程序的路径
println!("My path is {}.", args[0]);
// 其余的参数是被传递给程序的命令行参数。
// 请这样调用程序:
// $ ./args arg1 arg2
println!("I got {:?} arguments: {:?}.", args.len() - 1, &args[1..]);
println!("Hello Rust");
}
// rustc args.rs
// ./args
编译运行:
PS F:\rustproject\rustbyexample\chapter20\example20_13> rustc args.rs
PS F:\rustproject\rustbyexample\chapter20\example20_13> ./args 1 2 3 4
My path is F:\rustproject\rustbyexample\chapter20\example20_13\args.exe.
I got 4 arguments: ["1", "2", "3", "4"].
Hello Rust
PS F:\rustproject\rustbyexample\chapter20\example20_13>

crate
另外,也有很多 crate 提供了编写命令行应用的额外功能。Rust Cookbook 展示了使用最流行的命令行参数 crate,即 clap 的最佳实践。
20.7.1 参数解析
可以用模式匹配来解析简单的参数:
// 20.7.1节 参数解析
use std::env;
fn increase(number: i32) {
println!("{}", number + 1);
}
fn decrease(number: i32) {
println!("{}", number - 1);
}
fn help() {
println!("usage:
match_args <string>
Check whether given string is the answer.
match_args {{increase|decrease}} <integer>
Increase or decrease given integer by one.");
}
fn main() {
let args: Vec<String> = env::args().collect();
// 第一个参数是调用本程序的路径
println!("My path is {}", args[0]);
//println!("I got {:?} arguments: {:?}.", args.len() - 1, &args[1..]);
match args.len() {
// 没有传入参数
1 => {
println!("My name is 'match_args'. Try passing some arguments!");
},
// 一个传入参数
2 => {
match args[1].parse() {
Ok(42) => println!("This is the answer!"),
_ => println!("This is not the answer."),
}
},
// 传入一条命令和一个参数
3 => {
let cmd = &args[1];
let num = &args[2];
// 解析数字
let number: i32 = match num.parse() {
Ok(n) => {
n
},
Err(_) => {
println!("error: second argument not an integer");
help();
return;
},
};
// 解析命令
match &cmd[..] {
"increase" => increase(number),
"decrease" => decrease(number),
_ => {
println!("error: invalid command");
help();
},
}
},
// 所有其他情况
_ => {
// 显示帮助信息
help();
}
}
//println!("Hello Rust");
}
// rustc match_args.rs
// ./match_args
编译运行:
PS F:\rustproject\rustbyexample\chapter20\example20_14> rustc match_args.rs
My path is F:\rustproject\rustbyexample\chapter20\example20_14\match_args.exe
This is not the answer.
PS F:\rustproject\rustbyexample\chapter20\example20_14> ./match_args 42
My path is F:\rustproject\rustbyexample\chapter20\example20_14\match_args.exe
This is the answer!
PS F:\rustproject\rustbyexample\chapter20\example20_14> ./match_args do something
My path is F:\rustproject\rustbyexample\chapter20\example20_14\match_args.exe
error: second argument not an integer
usage:
match_args <string>
Check whether given string is the answer.
match_args {increase|decrease} <integer>
Increase or decrease given integer by one.
PS F:\rustproject\rustbyexample\chapter20\example20_14> ./match_args do 42
My path is F:\rustproject\rustbyexample\chapter20\example20_14\match_args.exe
error: invalid command
usage:
match_args <string>
Check whether given string is the answer.
match_args {increase|decrease} <integer>
Increase or decrease given integer by one.
PS F:\rustproject\rustbyexample\chapter20\example20_14>

20.8 外部语言函数接口
Rust 提供了到 C 语言库的外部语言函数接口(Foreign Function Interface,FFI)。外部语言函数必须在一个 extern 代码块中声明,且该代码块要带有一个包含库名称的 #[link] 属性。
// 20.8节 外部语言函数接口
use std::fmt;
// 这个 extern 代码块链接到 libm 库
#[link(name = "m")]
extern "C" {
// 这个外部函数用于计算单精度复数的平方根
fn csqrtf(z: Complex) -> Complex;
// 这个用来计算单精度复数的复变余弦
fn ccosf(z: Complex) -> Complex;
}
// 由于调用其他语言的函数被认为是不安全的,我们通常会给它们写一层安全的封装
fn cos(z: Complex) -> Complex {
unsafe{ ccosf(z) }
}
fn main() {
// z = -1 + 0i
let z = Complex { re: -1., im: 0. };
// 调用外部语言函数是不安全操作
let z_sqrt = unsafe {csqrtf(z)};
println!("the square root of {:?} is {:?}", z, z_sqrt);
// 调用不安全操作的安全的 API 封装
println!("cos({:?}) = {:?}", z, cos(z));
println!("Hello Rust");
}
// 单精度复数的最简实现
#[repr(C)]
#[derive(Clone, Copy)]
struct Complex {
re: f32,
im: f32,
}
impl fmt::Debug for Complex {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.im < 0. {
write!(f, "{}-{}i", self.re, -self.im)
} else {
write!(f, "{}-{}i", self.re, self.im)
}
}
}
//ubuntu2004环境编译
// rustc linklib.rs
// ./linklib
编译运行:
scott@ubuntu2004:~/trunk/rustbyexample/chapter20$ rustc linklib.rs
scott@ubuntu2004:~/trunk/rustbyexample/chapter20$ ./linklib
the square root of -1-0i is 0-1i
cos(-1-0i) = 0.5403023-0i
Hello Rust
scott@ubuntu2004:~/trunk/rustbyexample/chapter20$
