标准库提供的其他类型

  • 线程(Threads)
  • 信道(Channel)
  • 文件输入输出(File I/O)

这些内容在原生类型(第二节)进行了有效的扩充。

线程

Rust 通过 spawn 函数提供了创建本地操作系统(native OS)线程的机制。

  1. use std::thread;
  2. static NTHREADS: i32 = 10000;
  3. // 这是主(`main`)线程
  4. fn main() {
  5. // 提供一个 vector 来存放所创建的子线程(children)。
  6. let mut children = vec![];
  7. for i in 0..NTHREADS {
  8. // 启动(spin up)另一个线程
  9. children.push(thread::spawn(move || {
  10. println!("this is thread number {}", i)
  11. }));
  12. }
  13. for child in children {
  14. // 等待线程结束。返回一个结果。
  15. let _ = child.join();
  16. }
  17. }
  18. // 这些线程由操作系统调度(schedule)。

测试:map-reduce

标准库提供了开箱即用的线程类型,结合所有权概念和别名规则,自动地避免了数据竞争(data race)。
当某状态对某线程是可见的,别名规则(即一个可变引用 XOR 一些只读引用。译注:XOR 是异或的意思,即「二者仅居其一」)就自动地避免了别的线程对它的操作。(当需要同步 处理时,请使用 MutexChannel 这样的同步类型。)

  1. use std::thread;
  2. // 这是 `main` 线程
  3. fn main() {
  4. // 这是我们要处理的数据。
  5. // 我们会通过线程实现 map-reduce 算法,从而计算每一位的和
  6. // 每个用空白符隔开的块都会分配给单独的线程来处理
  7. //
  8. // 试一试:插入空格,看看输出会怎样变化!
  9. let data = "86967897737416471853297327050364959
  10. 11861322575564723963297542624962850
  11. 70856234701860851907960690014725639
  12. 3839796670710609417278 3238747669219
  13. 52380795257888236525459303330302837
  14. 58495327135744041048897885734297812
  15. 699202164389808735488 08413720956532
  16. 16278424637452589860345374828574668";
  17. // 创建一个向量,用于储存将要创建的子线程
  18. let mut children = vec![];
  19. /*************************************************************************
  20. * "Map" 阶段
  21. *
  22. * 把数据分段,并进行初始化处理
  23. ************************************************************************/
  24. // 把数据分段,每段将会单独计算
  25. // 每段都是完整数据的一个引用(&str)
  26. let chunked_data = data.split_whitespace();
  27. // 对分段的数据进行迭代。
  28. // .enumerate() 会把当前的迭代计数与被迭代的元素以元组 (index, element)
  29. // 的形式返回。接着立即使用 “解构赋值” 将该元组解构成两个变量,
  30. // `i` 和 `data_segment`。
  31. for (i, data_segment) in chunked_data.enumerate() {
  32. println!("data segment {} is \"{}\"", i, data_segment);
  33. // 用单独的线程处理每一段数据
  34. //
  35. // spawn() 返回新线程的句柄(handle),我们必须拥有句柄,
  36. // 才能获取线程的返回值。
  37. //
  38. // 'move || -> u32' 语法表示该闭包:
  39. // * 没有参数('||')
  40. // * 会获取所捕获变量的所有权('move')
  41. // * 返回无符号 32 位整数('-> u32')
  42. //
  43. // Rust 可以根据闭包的内容推断出 '-> u32',所以我们可以不写它。
  44. //
  45. // 试一试:data_segment 会存活到系统结束,将所有权转移
  46. children.push(thread::spawn(move || -> u32 {
  47. // 计算该段的每一位的和:
  48. let result = data_segment
  49. // 对该段中的字符进行迭代..
  50. .chars()
  51. // ..把字符转成数字..
  52. .map(|c| c.to_digit(10).expect("should be a digit"))
  53. // ..对返回的数字类型的迭代器求和
  54. .sum();
  55. // println! 会锁住标准输出,这样各线程打印的内容不会交错在一起
  56. println!("processed segment {}, result={}", i, result);
  57. // 不需要 “return”,因为 Rust 是一种 “表达式语言”,每个代码块中
  58. // 最后求值的表达式就是代码块的值。
  59. result
  60. }));
  61. }
  62. /*************************************************************************
  63. * "Reduce" 阶段
  64. *
  65. * 收集中间结果,得出最终结果
  66. ************************************************************************/
  67. // 把每个线程产生的中间结果收入一个新的向量中
  68. let mut intermediate_sums = vec![];
  69. for child in children {
  70. // 收集每个子线程的返回值
  71. let intermediate_sum = child.join().unwrap();
  72. intermediate_sums.push(intermediate_sum);
  73. }
  74. // 把所有中间结果加起来,得到最终结果
  75. //
  76. // 我们用 “涡轮鱼” 写法 ::<> 来为 sum() 提供类型提示。
  77. //
  78. // 显式地指定 intermediate_sums 是只需要给 final_result 加显示类型
  79. let final_result = intermediate_sums.iter().sum::<u32>();
  80. println!("Final sum result: {}", final_result);
  81. }

待改进:使得数据总是被分成有限数目的段,这个数目是由程序开头的静态常量决定的。

通道

Rust 为线程之间的通信提供了异步的通道(channel)。通道允许两个端点之间信息的单向流动:Sender(发送端)和 Receiver(接收端)。

  1. use std::sync::mpsc::{Sender, Receiver};
  2. use std::sync::mpsc;
  3. use std::thread;
  4. static NTHREADS: i32 = 3;
  5. fn main() {
  6. // 通道有两个端点:`Sender<T>` 和 `Receiver<T>`,其中 `T` 是要发送
  7. // 的消息的类型(类型标注是可选的)
  8. let (tx, rx): (Sender<i32>, Receiver<i32>) = mpsc::channel();
  9. for id in 0..NTHREADS {
  10. // sender 端可被复制
  11. let thread_tx = tx.clone();
  12. // 每个线程都将通过通道来发送它的 id
  13. thread::spawn(move || {
  14. // 被创建的线程取得 `thread_tx` 的所有权
  15. // 每个线程都把消息放在通道的消息队列中
  16. thread_tx.send(id).unwrap();
  17. // 发送是一个非阻塞(non-blocking)操作,线程将在发送完消息后
  18. // 会立即继续进行
  19. println!("thread {} finished", id);
  20. });
  21. }
  22. // 所有消息都在此处被收集
  23. let mut ids = Vec::with_capacity(NTHREADS as usize);
  24. for _ in 0..NTHREADS {
  25. // `recv` 方法从通道中拿到一个消息
  26. // 若无可用消息的话,`recv` 将阻止当前线程
  27. ids.push(rx.recv());
  28. }
  29. // 显示消息被发送的次序
  30. println!("{:?}", ids);
  31. }
  32. // 输出,其中 thread 2 没有输出时主线程已经关闭
  33. // thread 0 finished
  34. // thread 1 finished
  35. // [Ok(0), Ok(1), Ok(2)]

路径

Path 结构体代表了底层文件系统的文件路径。Path 分为两种:posix::Path,针对 类 UNIX 系统;以及 windows::Path,针对 Windows。prelude 会选择并输出符合平台类型 的 Path 种类。
Path 在内部并不是用 UTF-8 字符串表示的,而是存储为若干字节( Vec<u8> )的 vector。因此,将 Path 转化成 &tr 并非零开销,且可能失败,它返回一个 Option

  1. use std::path::Path;
  2. fn main() {
  3. // 从 `&'static str` 创建一个 `Path`
  4. let path = Path::new(".");
  5. // `display` 方法返回一个可显示(showable)的结构体
  6. let display = path.display();
  7. // `join` 使用操作系统特定的分隔符来合并路径到一个字节容器,并返回新的路径
  8. let new_path = path.join("a").join("b");
  9. // 将路径转换成一个字符串切片
  10. match new_path.to_str() {
  11. None => panic!("new path is not a valid UTF-8 sequence"),
  12. Some(s) => println!("new path is {}", s), // ./a/b
  13. }
  14. }

参见

OsStrMetadata

文件输入输出

File 结构体表示一个被打开的文件(它包裹了一个文件描述符),并赋予了对表示文件的读写能力。
File 的所有方法都返回了 io::Result<T> 类型,它是 Result<T, io::Error> 的别名。
所有 I/O 操作的失败都变成显式的。

打开文件

open 静态方法能够以只读模式(read-only mode)打开一个文件。
File 拥有资源,即文件描述符(file descriptor),它会在自身被 drop 时关闭文件。

  1. use std::error::Error;
  2. use std::fs::File;
  3. use std::io::prelude::*;
  4. use std::path::Path;
  5. fn main() {
  6. // 创建指向所需的文件的路径
  7. let path = Path::new("hello.txt");
  8. let display = path.display();
  9. // 以只读方式打开路径,返回 `io::Result<File>`
  10. let mut file = match File::open(&path) {
  11. // `io::Error` 的 `description` 方法返回一个描述错误的字符串。
  12. Err(why) => panic!("couldn't open {}: {}", display,
  13. why.description()),
  14. Ok(file) => file,
  15. };
  16. // 读取文件内容到一个字符串,返回 `io::Result<usize>`
  17. let mut s = String::new();
  18. match file.read_to_string(&mut s) {
  19. Err(why) => panic!("couldn't read {}: {}", display,
  20. why.description()),
  21. Ok(_) => print!("{} contains:\n{}", display, s),
  22. }
  23. // `file` 离开作用域,并且 `hello.txt` 文件将被关闭。
  24. }

创建文件

create 静态方法以只写模式(write-only mode)打开一个文件。若文件已经存在,则 旧内容将被销毁。否则,将创建一个新文件。

  1. static LOREM_IPSUM: &'static str = "Lorem ipsum dolor sit amet";
  2. use std::error::Error;
  3. use std::io::prelude::*;
  4. use std::fs::File;
  5. use std::path::Path;
  6. fn main() {
  7. let path = Path::new("out/lorem_ipsum.txt");
  8. let display = path.display();
  9. // 以只写模式打开文件,返回 `io::Result<File>`
  10. let mut file = match File::create(&path) {
  11. Err(why) => panic!("couldn't create {}: {}",
  12. display,
  13. why.description()),
  14. Ok(file) => file,
  15. };
  16. // 将 `LOREM_IPSUM` 字符串写进 `file`,返回 `io::Result<()>`
  17. match file.write_all(LOREM_IPSUM.as_bytes()) {
  18. Err(why) => {
  19. panic!("couldn't write to {}: {}", display,
  20. why.description())
  21. },
  22. Ok(_) => println!("successfully wrote to {}", display),
  23. }
  24. }

open_mode :更加通用,以其他方式打开文件,如:read+write,append 等。

读取行

方法 lines() 在文件的行上返回一个迭代器。
应该是 str 实现了 AsRef`` 的 trait。

  1. use std::fs::File;
  2. use std::io::{self, BufRead};
  3. use std::path::Path;
  4. fn main() {
  5. // 在生成输出之前,文件主机必须存在于当前路径中
  6. if let Ok(lines) = read_lines("./hosts") {
  7. // 使用迭代器,返回一个(可选)字符串
  8. for line in lines {
  9. if let Ok(ip) = line {
  10. println!("{}", ip);
  11. }
  12. }
  13. }
  14. }
  15. // 输出包裹在 Result 中以允许匹配错误,
  16. // 将迭代器返回给文件行的读取器(Reader)。
  17. fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
  18. where P: AsRef<Path>, {
  19. let file = File::open(filename)?;
  20. Ok(io::BufReader::new(file).lines())
  21. }

运行结果:

$ rustc read_lines.rs && ./read_lines 127.0.0.1

这个过程比在内存中创建 String 更有效,特别是处理更大的文件。

子进程

process::Output 结构体表示已结束的子进程(child process)的输出,而 process::Command 结构体是一个进程创建者(process builder)。

创建一个命令行执行的例子(rustc —version):

  1. use std::process::Command;
  2. fn main() {
  3. let output = Command::new("rustc")
  4. .arg("--version")
  5. .output().unwrap_or_else(|e| {
  6. panic!("failed to execute process: {}", e)
  7. });
  8. if output.status.success() {
  9. let s = String::from_utf8_lossy(&output.stdout);
  10. print!("rustc succeeded and stdout was:\n{}", s);
  11. } else {
  12. let s = String::from_utf8_lossy(&output.stderr);
  13. print!("rustc failed and stderr was:\n{}", s);
  14. }
  15. }

管道

std::Child 结构体代表了一个正在运行的子进程,它暴露了 stdin(标准 输入),stdout(标准输出) 和 stderr(标准错误) 句柄,从而可以通过管道与 所代表的进程交互。

  1. use std::error::Error;
  2. use std::io::prelude::*;
  3. use std::process::{Command, Stdio};
  4. static PANGRAM: &'static str =
  5. "the quick brown fox jumped over the lazy dog\n";
  6. fn main() {
  7. // 启动 `wc` 命令
  8. let process = match Command::new("wc")
  9. .stdin(Stdio::piped())
  10. .stdout(Stdio::piped())
  11. .spawn() {
  12. Err(why) => panic!("couldn't spawn wc: {}", why.description()),
  13. Ok(process) => process,
  14. };
  15. // 将字符串写入 `wc` 的 `stdin`。
  16. //
  17. // `stdin` 拥有 `Option<ChildStdin>` 类型,不过我们已经知道这个实例不为空值,
  18. // 因而可以直接 `unwrap 它。
  19. match process.stdin.unwrap().write_all(PANGRAM.as_bytes()) {
  20. Err(why) => panic!("couldn't write to wc stdin: {}",
  21. why.description()),
  22. Ok(_) => println!("sent pangram to wc"),
  23. }
  24. // 因为 `stdin` 在上面调用后就不再存活,所以它被 `drop` 了,管道也被关闭。
  25. //
  26. // 这点非常重要,因为否则 `wc` 就不会开始处理我们刚刚发送的输入。
  27. // `stdout` 字段也拥有 `Option<ChildStdout>` 类型,所以必需解包。
  28. let mut s = String::new();
  29. match process.stdout.unwrap().read_to_string(&mut s) {
  30. Err(why) => panic!("couldn't read wc stdout: {}",
  31. why.description()),
  32. Ok(_) => print!("wc responded with:\n{}", s),
  33. }
  34. }

结果:

sent pangram to wc wc responded with: 1 9 45

等待

如果你想等待一个 process::Child 完成,就必须调用 Child::wait,这会返回 一个 process::ExitStatus。 ```rust use std::process::Command;

fn main() { let mut child = Command::new(“sleep”).arg(“5”).spawn().unwrap(); let _result = child.wait().unwrap();

  1. println!("reached end of main");

}

  1. ```shell
  2. $ rustc wait.rs && ./wait
  3. reached end of main
  4. # `wait` keeps running for 5 seconds
  5. # `sleep 5` command ends, and then our `wait` program finishes

文件系统操作

std::io::fs 模块包含几个处理文件系统的函数。

  1. use std::fs::{self, File, OpenOptions};
  2. use std::io::{self, prelude::*, BufRead};
  3. use std::path::Path;
  4. // `% cat path` 的简单实现
  5. fn cat(path: &Path) -> io::Result<String> {
  6. let mut f = File::open(path)?;
  7. let mut s = String::new();
  8. f.read_to_string(&mut s)?;
  9. Ok(s)
  10. }
  11. // `% echo s > path` 的简单实现
  12. fn echo(s: &str, path: &Path) -> io::Result<()> {
  13. let mut f = File::create(path)?;
  14. f.write_all(s.as_bytes())
  15. }
  16. // `% touch path` 的简单实现(忽略已存在的文件)
  17. fn touch(path: &Path) -> io::Result<()> {
  18. match OpenOptions::new().create(true).write(true).open(path) {
  19. Ok(_) => Ok(()),
  20. Err(e) => Err(e),
  21. }
  22. }

程序参数

标准库

命令行参数可使用 std::env::args 进行接收,这将返回一个迭代器,该迭代器会对 每个参数举出一个字符串。

  1. use std::env;
  2. fn main() {
  3. let args: Vec<String> = env::args().collect();
  4. // 第一个参数是调用本程序的路径
  5. println!("My path is {}.", args[0]);
  6. // 其余的参数是被传递给程序的命令行参数。
  7. // 请这样调用程序:
  8. // $ ./args arg1 arg2
  9. println!("I got {:?} arguments: {:?}.", args.len() - 1, &args[1..]);
  10. }

$ ./args 1 2 3 My path is ./args. I got 3 arguments: [“1”, “2”, “3”].

crate

很多 crate 提供了编写命令行应用的额外功能。 clap 的最佳实践可以参考 Rust Cookbook

参数解析

可以使用模式匹配来解析简单的参数。

  • 没有输入参数;
  • 输入一个参数必须为数字;
  • 输入两个参数第一个是「命令」,第二个是数字;
  • 其他情况输出帮助。 ```rust use std::env;

fn increase(number: i32) { println!(“{}”, number + 1); }

fn decrease(number: i32) { println!(“{}”, number - 1); }

fn help() { println!(“usage: match_args Check whether given string is the answer. match_args {{increase|decrease}} Increase or decrease given integer by one.”); }

fn main() { let args: Vec = env::args().collect();

  1. match args.len() {
  2. // 没有传入参数
  3. 1 => {
  4. println!("My name is 'match_args'. Try passing some arguments!");
  5. },
  6. // 一个传入参数
  7. 2 => {
  8. match args[1].parse() {
  9. Ok(42) => println!("This is the answer!"),
  10. _ => println!("This is not the answer."),
  11. }
  12. },
  13. // 传入一条命令和一个参数
  14. 3 => {
  15. let cmd = &args[1];
  16. let num = &args[2];
  17. // 解析数字
  18. let number: i32 = match num.parse() {
  19. Ok(n) => {
  20. n
  21. },
  22. Err(_) => {
  23. println!("error: second argument not an integer");
  24. help();
  25. return;
  26. },
  27. };
  28. // 解析命令
  29. match &cmd[..] {
  30. "increase" => increase(number),
  31. "decrease" => decrease(number),
  32. _ => {
  33. println!("error: invalid command");
  34. help();
  35. },
  36. }
  37. },
  38. // 所有其他情况
  39. _ => {
  40. // 显示帮助信息
  41. help();
  42. }
  43. }

}

  1. <a name="JpQoe"></a>
  2. ## 外部语言函数接口(FFI)
  3. C 语言库外部语言函数接口(Foreign Function Interface,FFI)。外部语言函数必须在一个 `extern` 代码块中声明,且该代码要带有一个包含库名称的 `#[link]` 属性。
  4. ```rust
  5. use std::fmt;
  6. // 这个 extern 代码块链接到 libm 库
  7. #[link(name = "m")]
  8. extern {
  9. // 这个外部函数用于计算单精度复数的平方根
  10. fn csqrtf(z: Complex) -> Complex;
  11. // 这个用来计算单精度复数的复变余弦
  12. fn ccosf(z: Complex) -> Complex;
  13. }
  14. // 由于调用其他语言的函数被认为是不安全的,我们通常会给它们写一层安全的封装
  15. fn cos(z: Complex) -> Complex {
  16. unsafe { ccosf(z) }
  17. }
  18. fn main() {
  19. // z = -1 + 0i
  20. let z = Complex { re: -1., im: 0. };
  21. // 调用外部语言函数是不安全操作
  22. let z_sqrt = unsafe { csqrtf(z) };
  23. println!("the square root of {:?} is {:?}", z, z_sqrt);
  24. // 调用不安全操作的安全的 API 封装
  25. println!("cos({:?}) = {:?}", z, cos(z));
  26. }
  27. // 单精度复数的最简实现
  28. #[repr(C)]
  29. #[derive(Clone, Copy)]
  30. struct Complex {
  31. re: f32,
  32. im: f32,
  33. }
  34. impl fmt::Debug for Complex {
  35. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  36. if self.im < 0. {
  37. write!(f, "{}-{}i", self.re, -self.im)
  38. } else {
  39. write!(f, "{}+{}i", self.re, self.im)
  40. }
  41. }
  42. }

C 语言内存复制

  1. extern "C" {
  2. pub fn memcmp(s1: *const u8, s2: *const u8, n: usize) -> i32;
  3. }