标准库也提供了很多其他类型来支持某些功能,例如:
- 线程(Threads)
- 信道(Channels)
- 文件输入输出(File IO)
线程
Rust通过spawn函数提供了创建本地操作系统线程的机制,该函数的参数是一个通过值捕获变量的闭包(moveing closure)
rust
use std::thread;
static NTHREADS: i32 = 300;
// 这是主线程`main`
fn main() {
// 提供一个vector用来存放所创建的子线程(children)
let mut children = Vec::new();
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();
}
}
这些线程由操作系统调度
测试用例:map-reduce
Rust使数据的并行化处理非常简单,在Rust中您无需面对并行处理的很多传统难题
标准库提供了开箱即用的线程类型,把它和Rust的所有权概念与别名规则结合在一起,可以自动的避免数据竞争
当某状态对某线程是可见的,别名规则(即一个可变引用XOR一些只读引用。译注:XOR是异或的意思,即【二者仅局其一】)就自动避免了别的线程对他的操作。(当需要同步处理时,请使用Mutes或Channel这样的同步类型)
我们将会计算一堆数字中每一位的和。我们将把他们分为几块,放入不同的线程。每个线程会把自己那一块数字的每一位加起来,之后我们再把每个线程提供的结果再加起来
注意到,虽然我们在线程之间传递了引用,但Rust立即我们是在传递只读的引用,因此不会发生数据竞争等不安全的事情。另外,因为我们把数据库move到了线程中,Rust会保证数据存活到线程退出,因此不会产生悬垂指针
rust
use std::thread;
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();
for (i, data_segment) in chunked_data.enumerate() {
println!("data segment {} is "{}"", i, data_segment);
// 用单独的线程处理每一段数据
// spawn()返回新线程的句柄(handle),我们必须拥有句柄
// 才能获取线程的返回值
//
// move || ->u32 语法表示该闭包
//
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);
result
}))
}
// Reduce阶段
// 收集中间结果,得出最终结果
// 把每个线程产生的中间结果收入一个新的向量中
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_result);
}
通道
Rust为线程之间的通信提供了异步的通道(channel),通道允许两个端点之间信息的单向流动:Sender发送端 和 Receiver(接收端)
rust
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::<i32>();
for id in 0..NTHREADS {
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);
}
路径
Path结构体代表了底层文件系统的文件路径。Path分为两种:posix::Path,针对类Unix系统;以及windows::Path,针对windows。prelude会选择并输出符合平台类型的Path种类
Path可从OsStr类型创建,并且他提供数种方法,用于获取路径指向的文件/目录的信息 注意Path在内部并不是用UTF-8字符串表示的,而是存储为若干字节(Vec<u8>)的vector。因此,将Path转化成&str并非零开销的(free),且可能失败(因此它返回一个Option)
rust
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),
}
}
文件输入输出(I/O)
File结构体表示一个被打开的文件(他包裹了一个文件描述符),并赋予了对所表示文件的读写能力。 由于在进行文件I/O(输入/输出)操作时可能出现各种错误,因此File的所有方法都返回io::Result<T>类型,他是<T,io::Error>的别名 这使得所有 I/O 操作的失败都变成显式的。借助这点,程序员可以看到所有的失败路径,并被鼓励主动地处理这些情形
打开文件open
open 静态方法能够以只读模式(read-only mode)打开一个文件。
File 拥有资源,即文件描述符(file descriptor),它会在自身被 drop 时关闭文件。
rust
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),
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{}", display, s),
}
// `file` 离开作用域,并且 `hello.txt` 文件将被关闭。
}
创建文件create
create 静态方法以只写模式(write-only mode)打开一个文件。若文件已经存在,则旧内容将被销毁。否则,将创建一个新文件。
rust
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("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),
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),
}
}
读取行
方法 lines() 在文件的行上返回一个迭代器
File::open 需要一个泛型 AsRef<Path>。这正是 read_lines() 期望的输入
rust
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())
}
子进程
process::Output结构体表示已结束的子进程的输出,而 process::Command 结构体是一个进程创建者
rust
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(标准错误)句柄,从而可以通过管道与所代表的进程交互。
rust
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!("wc responded with:\n{}", s),
}
}
等待
如果你想等待一个 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");
}
文件操作系统
std::io::fs 模块包含几个处理文件系统的函数
rust
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());
});
}
程序参数
命令行参数可使用 std::env::args 进行接收,这将返回一个迭代器,该迭代器会对每个参数举出一个字符串。
rust
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..]);
}