输入和输出建立在三个trait的基础上:Read,BufRead,Write。一些例子:
pr2e_1801.png
因为这三个很常用,所以有prelude:use std::io::prelude::*;

Readers

std::io::Read用来读数据的几个方法,都是适用reader的可变引用。

reader

reader.read(&mut buffer),从reader中读取数据写入buffer。buffer是&mut [u8],读最多buffer.len()个字节的数据。返回io::Result。错误类型是io::Error,它有一个kind方法返回io::ErrorKind,io::ErrorKind是一个枚举,表示不同的错误原因,比如:PermissionDenied ,ConnectionReset,Interrupted。read是一个很底层的方法。

常用的方法还有:reader.read_to_end(&mut byte_vec),byte_vec.len()最自动变大;reader.read_to_string(&mut string),会有可能ErrorKind::InvalidData;reader.read_exact(&mut buf),如果buf不够会报ErrorKind::UnexpectedEof

reader的迭代适配器:reader.bytes(),元素是io::Result,底层是每个字节调用一次read()如果没有buffered会效率非常低;reader.chain(reader2);reader.take(n)。

Buffered Readers

为了高效,在内存里有一块内存,用来缓存输入输出的数据,这样可以节省系统调用。例如read_line方法每次调用系统api读硬盘数据的时候会读很多,存到buffer,然后reader再掉,只取一行
pr2e_1802.png
buffered reader需要实现两个trait,Read和BufRead。BufRead包括以下方法:
reader.read_line(&mut line),行末的\n或\r\n会保留;
reader.lines(),返回迭代器,元素是io::Result,行末的\n或\r\n不会保留;
reader.read_until(stop_byte, &mut byte_vec), reader.split(stop_byte);

还可以直接操作底层的buffer:.fill_buf()和.consume(n)

stdin没有实现BufRead,因为一个线程直接读Buf可能会竞争?所以Rust用mutex保护了stdin,需要用lock方法获取StdinLock,StdinLock实现了BufRead,然后才可以用lines来获取标准输入的行的迭代器。

Rust的文件本身也是没有实现BufRead的,任何Read想要变成BufRead都可以自己加:BufReader::new(reader)。指定buffer的大小:BufReader::with_capacity(size, reader)。
其他语言的文件都自带buffer,Rust把控制权交给程序员。

因为lines返回的元素是Result,所以不能直接collect成Vec,因为collect的结果会是Vec>。但因为Result有一个特殊的FromIterator的实现

  1. impl<T, E, C> FromIterator<Result<T, E>> for Result<C, E>
  2. where C: FromIterator<T>

lines可以collect成Result>。

Writers

常用的print!和println!是特殊形式的write!和writeln!。

  1. writeln!(io::stderr(), "error: world not helloable")?;
  2. writeln!(&mut byte_vec, "The greatest common divisor of {:?} is {}",
  3. numbers, d)?;

都是将字符串写入到某个writer。

writer有以下方法:
writer.write(&buf):将buf中的一些字节写入的writer底层的stream中,是底层的方法。
writer.write_all(&buf):将buf中所有的字节写入。
writer.flush():将所有缓存的数据写入流中,println! eprintln!都自动调了flush,print! eprint!都没有调flush。

BufWriter::new(writer)可以给writer加buffer。buffer在drop的时候会自动把内容flush到writer,这个过程中如果发送错误会被忽略,因为都已经drop不忽略也没用。所以最好手动flush,以免有没捕捉的错误。

文件

File::open(filename)打开一个文件用来读,返回Result,如果不存在报错
File::create(filename)创建一个文件用来写,如果存在就截断。
其他行为由OpenOptions来设置。

  1. use std::fs::OpenOptions;
  2. let log = OpenOptions::new()
  3. .append(true) // if file exists, add to the end
  4. .open("server.log")?;
  5. let file = OpenOptions::new()
  6. .write(true)
  7. .create_new(true) // fail if file exists
  8. .open("new_file.txt")?;

File实现了Seek,所以可以在文件里跳转到指定的位置:file.seek(SeekFrom::Start(0)),file.seek(SeekFrom::Current(-8))

  1. pub trait Seek {
  2. fn seek(&mut self, pos: SeekFrom) -> io::Result<u64>;
  3. }
  4. pub enum SeekFrom {
  5. Start(u64),
  6. End(i64),
  7. Current(i64)
  8. }

其他类型的读写

标准输入

io::stdin()返回一个Stdin。因为它被所有的线程共享,所以每个线程读的时候都要去得和释放锁。buffered操作需要调用lock方法获取锁,单独的读取一次可以省略这个操作。
不能直接调用io::stdin().lock(),因为锁持有一个Stdin的索引,所以Stdin本身必须保存在一个声明周期足够长的地方,所以必须保存在一个变量里:

  1. let stdin = io::stdin();
  2. let lines = stdin.lock().lines(); // ok

io::stdout(), io::stderr()

也有锁机制。

Vec和String

Vec实现了Write,写入Vec会让其扩展。String没有实现,必须先写到Vec,然后转成String:String::from_utf8(vec)

Cursor

一个buffered reader,Cursor::new(buf)创建一个从buf读的Cursor。buf可以是任何实现了AsRef<[u8]>的类型,包括:String,&[u8],&str,Vec[u8]。
Cursor实现了Read,bufRead,Seek。如果底层是&mut [u8]或Vec[u8],还实现了Write。写入到Cursor会覆盖从当前位置向后的内容。如果写入 &mut [u8],超过buf的长度可能截断或报错。写入Vec超过会扩展Vec。

std::net::TcpStream

因为TCP是双向的通信,所以TcpStream既是Read又是Write。TcpStream::connect((“hostname”, PORT))创建连接,返回io::Result

std::process::Command

用子进程运行一个命令,并通过管道写入他的标准输入。

  1. use std::process::{Command, Stdio};
  2. let mut child =
  3. Command::new("grep")
  4. .arg("-e")
  5. .arg("a.*e.*i.*o.*u")
  6. .stdin(Stdio::piped())
  7. .spawn()?;
  8. let mut to_child = child.stdin.take().unwrap();
  9. for word in my_words {
  10. writeln!(to_child, "{}", word)?;
  11. }
  12. drop(to_child); // close grep's stdin, so it will exit
  13. child.wait()?;

其他

io::sink()无操作的writer,
io::empty()无操作的reader
io::repeat(byte)永远重复一个字节的reader。

二进制数据,压缩,序列化

byteorder提供一些二进制数据的读写。
flate2提供一些压缩文件的支持。

serde

serde_json::to_writer可以将一些Rust的数据结构转成json的字符串字节。它用到了serde的Serialize trait。使用Serialize 和Deserialize两个trait可以derive自定义数据结构的序列化和反序列化trait。

因为derive会让编译时间边长,所以在依赖中要写明让serde支持。

  1. [dependencies]
  2. serde = { version = "1.0", features = ["derive"] }
  3. serde_json = "1.0"


文件和目录

操作系统不会去区别Unicode和随机的字节,所以文件名可以是任何的字节。因为Rust的字符串都必须是有效的Unicode所以无法读取随机的文件名。所以Rust有std::ffi::OsStr和OsString两种类型。OsString之于OsStr就行String之于str。std::path::Path和std::path::PathBuf专门处理文件名,多一些方便的方法。
这些方法包括String和str都实现了AsRed,所以写关于文件名的泛型时都可以用。

Path::new(str),将&str或&OsStr转成&Path,不会复制,只是生成一个指向原值的引用。
同理OsStr::new(str)将&str转成&OsStr。

path.parent()返回上一级目录

path.file_name()返回的其实是最后一级目录,就算不是文件名也会返回,就算最后一级后面还跟着一个/,例如/home/fwolfe/会返回fwolfe

path1.join(path2)会返回一个PathBuf。path2如果是绝对路径就会直接返回path2。join会根据不同的操作系统返回不同的分隔符。
PathBuf可以和Path直接判断相等。

path.read_dir()读取一个目录,返回一个io::Result,ReadDir实现了IntoIterator,元素是Result。DirEntry是一个目录或文件有以下方法:entry.file_name(),返回OSString;entry.path(),返回PathBuf::from(“…”);entry.file_type();entry.metadata()。复制一个目录到另一个目录的例子:

  1. use std::fs;
  2. use std::io;
  3. use std::path::Path;
  4. /// Copy the existing directory `src` to the target path `dst`.
  5. fn copy_dir_to(src: &Path, dst: &Path) -> io::Result<()> {
  6. if !dst.is_dir() {
  7. fs::create_dir(dst)?;
  8. }
  9. for entry_result in src.read_dir()? {
  10. let entry = entry_result?;
  11. let file_type = entry.file_type()?;
  12. copy_to(&entry.path(), &file_type, &dst.join(entry.file_name()))?;
  13. }
  14. Ok(())
  15. }
  16. /// Copy wha tever is at `src` to the target path `dst`.
  17. fn copy_to(src: &Path, src_type: &fs::FileType, dst: &Path)
  18. -> io::Result<()>
  19. {
  20. if src_type.is_file() {
  21. fs::copy(src, dst)?;
  22. } else if src_type.is_dir() {
  23. copy_dir_to(src, dst)?;
  24. } else {
  25. return Err(io::Error::new(io::ErrorKind::Other,
  26. format!("don't know how to copy: {}",
  27. src.display())));
  28. }
  29. Ok(())
  30. }

不同的操作系统有不同的功能和方法,使用cfg属性可以指定某些方法只在某个或不在某些系统编译

  1. #[cfg(unix)] pub mod unix;
  2. #[cfg(windows)] pub mod windows;
  3. #[cfg(target_os = "ios")] pub mod ios;
  4. #[cfg(target_os = "linux")] pub mod linux;
  5. #[cfg(target_os = "macos")] pub mod macos;
  6. #[cfg(unix)]
  7. use std::os::unix::fs::symlink;
  8. /// Stub implementation of `symlink` for platforms that don't provide it.
  9. #[cfg(not(unix))]
  10. fn symlink<P: AsRef<Path>, Q: AsRef<Path>>(src: P, _dst: Q)
  11. -> std::io::Result<()>
  12. {
  13. Err(io::Error::new(io::ErrorKind::Other,
  14. format!("can't copy symbolic link: {}",
  15. src.as_ref().display())))
  16. }

网络

相关的模块std::net。native_tls crate提供SSL/TLS。
一个tcp例子

  1. use std::net::TcpListener;
  2. use std::io;
  3. use std::thread::spawn;
  4. /// Accept connections forever, spawning a thread for each one.
  5. fn echo_main(addr: &str) -> io::Result<()> {
  6. let listener = TcpListener::bind(addr)?;
  7. println!("listening on {}", addr);
  8. loop {
  9. // Wait for a client to connect.
  10. let (mut stream, addr) = listener.accept()?;
  11. println!("connection received from {}", addr);
  12. // Spawn a thread to handle this client.
  13. let mut write_stream = stream.try_clone()?;
  14. spawn(move || {
  15. // Echo everything we receive from `stream` back to it.
  16. io::copy(&mut stream, &mut write_stream)
  17. .expect("error in client thread: ");
  18. println!("connection closed");
  19. });
  20. }
  21. }
  22. fn main() {
  23. echo_main("127.0.0.1:17007").expect("error: ");
  24. }

reqwest提供http client功能:

  1. use std::error::Error;
  2. use std::io;
  3. fn http_get_main(url: &str) -> Result<(), Box<dyn Error>> {
  4. // Send the HTTP request and get a response.
  5. let mut response = reqwest::blocking::get(url)?;
  6. if !response.status().is_success() {
  7. Err(format!("{}", response.status()))?;
  8. }
  9. // Read the response body and write it to stdout.
  10. let stdout = io::stdout();
  11. io::copy(&mut response, &mut stdout.lock())?;
  12. Ok(())
  13. }
  14. fn main() {
  15. let args: Vec<String> = std::env::args().collect();
  16. if args.len() != 2 {
  17. eprintln!("usage: http-get URL");
  18. return;
  19. }
  20. if let Err(err) = http_get_main(&args[1]) {
  21. eprintln!("error: {}", err);
  22. }
  23. }

actix-web,websocket