输入和输出建立在三个trait的基础上:Read,BufRead,Write。一些例子:
因为这三个很常用,所以有prelude:use std::io::prelude::*;
Readers
std::io::Read用来读数据的几个方法,都是适用reader的可变引用。
reader
reader.read(&mut buffer),从reader中读取数据写入buffer。buffer是&mut [u8],读最多buffer.len()个字节的数据。返回io::Result
常用的方法还有: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
Buffered Readers
为了高效,在内存里有一块内存,用来缓存输入输出的数据,这样可以节省系统调用。例如read_line方法每次调用系统api读硬盘数据的时候会读很多,存到buffer,然后reader再掉,只取一行
buffered reader需要实现两个trait,Read和BufRead。BufRead包括以下方法:
reader.read_line(&mut line),行末的\n或\r\n会保留;
reader.lines(),返回迭代器,元素是io::Result
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
impl<T, E, C> FromIterator<Result<T, E>> for Result<C, E>
where C: FromIterator<T>
lines可以collect成Result
Writers
常用的print!和println!是特殊形式的write!和writeln!。
writeln!(io::stderr(), "error: world not helloable")?;
writeln!(&mut byte_vec, "The greatest common divisor of {:?} is {}",
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来设置。
use std::fs::OpenOptions;
let log = OpenOptions::new()
.append(true) // if file exists, add to the end
.open("server.log")?;
let file = OpenOptions::new()
.write(true)
.create_new(true) // fail if file exists
.open("new_file.txt")?;
File实现了Seek,所以可以在文件里跳转到指定的位置:file.seek(SeekFrom::Start(0)),file.seek(SeekFrom::Current(-8))
pub trait Seek {
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64>;
}
pub enum SeekFrom {
Start(u64),
End(i64),
Current(i64)
}
其他类型的读写
标准输入
io::stdin()返回一个Stdin。因为它被所有的线程共享,所以每个线程读的时候都要去得和释放锁。buffered操作需要调用lock方法获取锁,单独的读取一次可以省略这个操作。
不能直接调用io::stdin().lock(),因为锁持有一个Stdin的索引,所以Stdin本身必须保存在一个声明周期足够长的地方,所以必须保存在一个变量里:
let stdin = io::stdin();
let lines = stdin.lock().lines(); // ok
io::stdout(), io::stderr()
也有锁机制。
Vec和String
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
用子进程运行一个命令,并通过管道写入他的标准输入。
use std::process::{Command, Stdio};
let mut child =
Command::new("grep")
.arg("-e")
.arg("a.*e.*i.*o.*u")
.stdin(Stdio::piped())
.spawn()?;
let mut to_child = child.stdin.take().unwrap();
for word in my_words {
writeln!(to_child, "{}", word)?;
}
drop(to_child); // close grep's stdin, so it will exit
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支持。
[dependencies]
serde = { version = "1.0", features = ["derive"] }
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
use std::fs;
use std::io;
use std::path::Path;
/// Copy the existing directory `src` to the target path `dst`.
fn copy_dir_to(src: &Path, dst: &Path) -> io::Result<()> {
if !dst.is_dir() {
fs::create_dir(dst)?;
}
for entry_result in src.read_dir()? {
let entry = entry_result?;
let file_type = entry.file_type()?;
copy_to(&entry.path(), &file_type, &dst.join(entry.file_name()))?;
}
Ok(())
}
/// Copy wha tever is at `src` to the target path `dst`.
fn copy_to(src: &Path, src_type: &fs::FileType, dst: &Path)
-> io::Result<()>
{
if src_type.is_file() {
fs::copy(src, dst)?;
} else if src_type.is_dir() {
copy_dir_to(src, dst)?;
} else {
return Err(io::Error::new(io::ErrorKind::Other,
format!("don't know how to copy: {}",
src.display())));
}
Ok(())
}
不同的操作系统有不同的功能和方法,使用cfg属性可以指定某些方法只在某个或不在某些系统编译
#[cfg(unix)] pub mod unix;
#[cfg(windows)] pub mod windows;
#[cfg(target_os = "ios")] pub mod ios;
#[cfg(target_os = "linux")] pub mod linux;
#[cfg(target_os = "macos")] pub mod macos;
#[cfg(unix)]
use std::os::unix::fs::symlink;
/// Stub implementation of `symlink` for platforms that don't provide it.
#[cfg(not(unix))]
fn symlink<P: AsRef<Path>, Q: AsRef<Path>>(src: P, _dst: Q)
-> std::io::Result<()>
{
Err(io::Error::new(io::ErrorKind::Other,
format!("can't copy symbolic link: {}",
src.as_ref().display())))
}
网络
相关的模块std::net。native_tls crate提供SSL/TLS。
一个tcp例子
use std::net::TcpListener;
use std::io;
use std::thread::spawn;
/// Accept connections forever, spawning a thread for each one.
fn echo_main(addr: &str) -> io::Result<()> {
let listener = TcpListener::bind(addr)?;
println!("listening on {}", addr);
loop {
// Wait for a client to connect.
let (mut stream, addr) = listener.accept()?;
println!("connection received from {}", addr);
// Spawn a thread to handle this client.
let mut write_stream = stream.try_clone()?;
spawn(move || {
// Echo everything we receive from `stream` back to it.
io::copy(&mut stream, &mut write_stream)
.expect("error in client thread: ");
println!("connection closed");
});
}
}
fn main() {
echo_main("127.0.0.1:17007").expect("error: ");
}
reqwest提供http client功能:
use std::error::Error;
use std::io;
fn http_get_main(url: &str) -> Result<(), Box<dyn Error>> {
// Send the HTTP request and get a response.
let mut response = reqwest::blocking::get(url)?;
if !response.status().is_success() {
Err(format!("{}", response.status()))?;
}
// Read the response body and write it to stdout.
let stdout = io::stdout();
io::copy(&mut response, &mut stdout.lock())?;
Ok(())
}
fn main() {
let args: Vec<String> = std::env::args().collect();
if args.len() != 2 {
eprintln!("usage: http-get URL");
return;
}
if let Err(err) = http_get_main(&args[1]) {
eprintln!("error: {}", err);
}
}
actix-web,websocket