标准库提供的其他类型
- 线程(Threads)
- 信道(Channel)
- 文件输入输出(File I/O)
这些内容在原生类型(第二节)进行了有效的扩充。
线程
Rust 通过 spawn 函数提供了创建本地操作系统(native OS)线程的机制。
use std::thread;static NTHREADS: i32 = 10000;// 这是主(`main`)线程fn main() {// 提供一个 vector 来存放所创建的子线程(children)。let mut children = vec![];for i in 0..NTHREADS {// 启动(spin up)另一个线程children.push(thread::spawn(move || {println!("this is thread number {}", i)}));}for child in children {// 等待线程结束。返回一个结果。let _ = child.join();}}// 这些线程由操作系统调度(schedule)。
测试:map-reduce
标准库提供了开箱即用的线程类型,结合所有权概念和别名规则,自动地避免了数据竞争(data race)。
当某状态对某线程是可见的,别名规则(即一个可变引用 XOR 一些只读引用。译注:XOR 是异或的意思,即「二者仅居其一」)就自动地避免了别的线程对它的操作。(当需要同步 处理时,请使用 Mutex 或 Channel 这样的同步类型。)
use std::thread;// 这是 `main` 线程fn main() {// 这是我们要处理的数据。// 我们会通过线程实现 map-reduce 算法,从而计算每一位的和// 每个用空白符隔开的块都会分配给单独的线程来处理//// 试一试:插入空格,看看输出会怎样变化!let data = "8696789773741647185329732705036495911861322575564723963297542624962850708562347018608519079606900147256393839796670710609417278 32387476692195238079525788823652545930333030283758495327135744041048897885734297812699202164389808735488 0841372095653216278424637452589860345374828574668";// 创建一个向量,用于储存将要创建的子线程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',所以我们可以不写它。//// 试一试:data_segment 会存活到系统结束,将所有权转移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 是只需要给 final_result 加显示类型let final_result = intermediate_sums.iter().sum::<u32>();println!("Final sum result: {}", final_result);}
待改进:使得数据总是被分成有限数目的段,这个数目是由程序开头的静态常量决定的。
通道
Rust 为线程之间的通信提供了异步的通道(channel)。通道允许两个端点之间信息的单向流动:Sender(发送端)和 Receiver(接收端)。
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();// 每个线程都将通过通道来发送它的 idthread::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);}// 输出,其中 thread 2 没有输出时主线程已经关闭// thread 0 finished// thread 1 finished// [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 。
use std::path::Path;fn main() {// 从 `&'static str` 创建一个 `Path`let path = Path::new(".");// `display` 方法返回一个可显示(showable)的结构体let display = path.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), // ./a/b}}
参见
文件输入输出
File 结构体表示一个被打开的文件(它包裹了一个文件描述符),并赋予了对表示文件的读写能力。File 的所有方法都返回了 io::Result<T> 类型,它是 Result<T, io::Error> 的别名。
所有 I/O 操作的失败都变成显式的。
打开文件
open 静态方法能够以只读模式(read-only mode)打开一个文件。File 拥有资源,即文件描述符(file descriptor),它会在自身被 drop 时关闭文件。
use std::error::Error;use std::fs::File;use std::io::prelude::*;use std::path::Path;fn main() {// 创建指向所需的文件的路径let path = Path::new("hello.txt");let display = path.display();// 以只读方式打开路径,返回 `io::Result<File>`let mut file = match File::open(&path) {// `io::Error` 的 `description` 方法返回一个描述错误的字符串。Err(why) => panic!("couldn't open {}: {}", display,why.description()),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.description()),Ok(_) => print!("{} contains:\n{}", display, s),}// `file` 离开作用域,并且 `hello.txt` 文件将被关闭。}
创建文件
create 静态方法以只写模式(write-only mode)打开一个文件。若文件已经存在,则 旧内容将被销毁。否则,将创建一个新文件。
static LOREM_IPSUM: &'static str = "Lorem ipsum dolor sit amet";use std::error::Error;use std::io::prelude::*;use std::fs::File;use std::path::Path;fn main() {let path = Path::new("out/lorem_ipsum.txt");let display = path.display();// 以只写模式打开文件,返回 `io::Result<File>`let mut file = match File::create(&path) {Err(why) => panic!("couldn't create {}: {}",display,why.description()),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.description())},Ok(_) => println!("successfully wrote to {}", display),}}
open_mode :更加通用,以其他方式打开文件,如:read+write,append 等。
读取行
方法 lines() 在文件的行上返回一个迭代器。
应该是 str 实现了 AsRef
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);}}}}// 输出包裹在 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 read_lines.rs && ./read_lines 127.0.0.1
这个过程比在内存中创建 String 更有效,特别是处理更大的文件。
子进程
process::Output 结构体表示已结束的子进程(child process)的输出,而 process::Command 结构体是一个进程创建者(process builder)。
创建一个命令行执行的例子(rustc —version):
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);}}
管道
std::Child 结构体代表了一个正在运行的子进程,它暴露了 stdin(标准 输入),stdout(标准输出) 和 stderr(标准错误) 句柄,从而可以通过管道与 所代表的进程交互。
use std::error::Error;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.description()),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.description()),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.description()),Ok(_) => print!("wc responded with:\n{}", s),}}
结果:
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();
println!("reached end of main");
}
```shell$ rustc wait.rs && ./waitreached end of main# `wait` keeps running for 5 seconds# `sleep 5` command ends, and then our `wait` program finishes
文件系统操作
std::io::fs 模块包含几个处理文件系统的函数。
use std::fs::{self, File, OpenOptions};use std::io::{self, prelude::*, BufRead};use std::path::Path;// `% cat path` 的简单实现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)}// `% 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),}}
程序参数
标准库
命令行参数可使用 std::env::args 进行接收,这将返回一个迭代器,该迭代器会对 每个参数举出一个字符串。
use std::env;fn main() {let args: Vec<String> = env::args().collect();// 第一个参数是调用本程序的路径println!("My path is {}.", args[0]);// 其余的参数是被传递给程序的命令行参数。// 请这样调用程序:// $ ./args arg1 arg2println!("I got {:?} arguments: {:?}.", args.len() - 1, &args[1..]);}
$ ./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
fn main() {
let args: Vec
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();}}
}
<a name="JpQoe"></a>## 外部语言函数接口(FFI)C 语言库外部语言函数接口(Foreign Function Interface,FFI)。外部语言函数必须在一个 `extern` 代码块中声明,且该代码要带有一个包含库名称的 `#[link]` 属性。```rustuse std::fmt;// 这个 extern 代码块链接到 libm 库#[link(name = "m")]extern {// 这个外部函数用于计算单精度复数的平方根fn csqrtf(z: Complex) -> Complex;// 这个用来计算单精度复数的复变余弦fn ccosf(z: Complex) -> Complex;}// 由于调用其他语言的函数被认为是不安全的,我们通常会给它们写一层安全的封装fn cos(z: Complex) -> Complex {unsafe { ccosf(z) }}fn main() {// z = -1 + 0ilet 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));}// 单精度复数的最简实现#[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)}}}
C 语言内存复制
extern "C" {pub fn memcmp(s1: *const u8, s2: *const u8, n: usize) -> i32;}
