Rust :: Files and I/O

Files, Input and Output

Process »

// use output
// default stdout is piped, so it's not printed to console
Command::new("sh")
        .arg("-c")
        .arg("echo hello")
        .output()
        .expect("failed to execute process");
println!("status: {}", output.status);
io::stdout().write_all(&output.stdout).unwrap();
io::stderr().write_all(&output.stderr).unwrap();
assert!(output.status.success());

// use spawn
// default stdout is inherit, so this will print out to console
Command::new("ls")
        .args(["-l", "-a"])
        .spawn()
        .expect("ls command failed to start")
        .wait()?;   // call wait to release resource 

// run a pipe `du -ah .| sort -hr | head -n 10`
use std::process::{Command, Stdio};
fn main() -> Result<()> {
    let directory = std::env::current_dir()?;
    // need to use mut
    let mut du_output_child = Command::new("du")
        .arg("-ah")
        .arg(&directory)
        .stdout(Stdio::piped())     // pipe stdout
        .spawn()?;

    if let Some(du_output) = du_output_child.stdout.take() {
        let mut sort_output_child = Command::new("sort")
            .arg("-hr")
            .stdin(du_output)   // pipe du_output to stdin of sort
            .stdout(Stdio::piped()) // pipe stdout
            .spawn()?;

        du_output_child.wait()?;        // release du

        if let Some(sort_output) = sort_output_child.stdout.take() {
            let head_output_child = Command::new("head")
                .args(&["-n", "10"])
                .stdin(sort_output)
                .stdout(Stdio::piped())
                .spawn()?;     // this can be replaced with .output() 

            let head_stdout = head_output_child.wait_with_output()?;

            sort_output_child.wait()?;

            println!(
                "Top 10 biggest files and directories in '{}':\n{}",
                directory.display(),
                String::from_utf8(head_stdout.stdout).unwrap()
            );
        }
    }

    Ok(())
}

// redirect stdout and stderr to file
use std::fs::File;
use std::io::Error;
use std::process::{Command, Stdio};

fn main() -> Result<(), Error> {
    let outputs = File::create("out.txt")?;
    let errors = outputs.try_clone()?;

    Command::new("ls")
        .args(&[".", "oops"])
        .stdout(outputs)            // same as Stdio::from(outputs)
        .stderr(Stdio::from(errors))    // same as `errors`
        .spawn()?
        .wait_with_output()?;   // same as .output()

    Ok(())
}

Environment »

OsStr and Path

OsStr and OsString

Path and PathBuf

pub struct PathBuf {
    inner: OsString,
}
pub struct PathBuf {
    inner: OsString,
}

File System »

OpenOptions

.new()              // return Self

.read(bool)         // the rest take bool other than open 
.write(bool)        // if file already exists, will overwrite without truncating
.append(bool)       // .write(true).append(true) is the same as .append(true)

.truncate(bool)     // file must be opened with write access for this to work
.create(bool)       // create a new, or open an existing one, used after .write or .append
.create_new(bool)   // only create, failing if one exists

.open(path)         // return Result<File>    

// File::open
OpenOptions::new().read(true).open(path.as_ref())
// File::create
OpenOptions::new().write(true).create(true).truncate(true).open(path.as_ref())

Input and Output »

Read and Write Traits

Read trait

pub trait Read {
    // required; low-level, avoid using directly
    fn read(&mut self, buf: &mut [u8]) -> Result<usize>;
    // provided
    fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> Result<usize> { ... }
    fn is_read_vectored(&self) -> bool { ... }
    unsafe fn initializer(&self) -> Initializer { ... }
    fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize> { ... }
    fn read_to_string(&mut self, buf: &mut String) -> Result<usize> { ... }
    fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> { ... }
    fn by_ref(&mut self) -> &mut Self
    where
        Self: Sized,
    { ... }
    fn bytes(self) -> Bytes<Self>
    where
        Self: Sized,
    { ... }
    fn chain<R: Read>(self, next: R) -> Chain<Self, R>
    where
        Self: Sized,
    { ... }
    fn take(self, limit: u64) -> Take<Self>
    where
        Self: Sized,
    { ... }
}
pub trait BufRead: Read {
    // required
    fn fill_buf(&mut self) -> Result<&[u8]>;
    fn consume(&mut self, amt: usize);
    // provided
    fn has_data_left(&mut self) -> Result<bool> { ... }
    fn read_until(&mut self, byte: u8, buf: &mut Vec<u8>) -> Result<usize> { ... }
    fn read_line(&mut self, buf: &mut String) -> Result<usize> { ... }
    fn split(self, byte: u8) -> Split<Self>
    where
        Self: Sized,
    { ... }
    fn lines(self) -> Lines<Self>
    where
        Self: Sized,
    { ... }
}
// collect Results
let lines = reader.lines().collect::<io::Result<Vec<String>>>()?;
// this takes advantage of `FromIterator` for `Result`
impl<T, E, C> FromIterator<Result<T, E>> for Result<C, E>
    where C: FromIterator<T>

Write trait

pub trait Write {
    // required, low-level
    fn write(&mut self, buf: &[u8]) -> Result<usize>;
    // ensures that all intermediately buffered contents reach their dest.
    // print! and eprint! do not call flush; call manually
    fn flush(&mut self) -> Result<()>;
    // provided
    fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> Result<usize> { ... }
    fn is_write_vectored(&self) -> bool { ... }
    fn write_all(&mut self, buf: &[u8]) -> Result<()> { ... }
    fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> Result<()> { ... }
    fn write_fmt(&mut self, fmt: Arguments<'_>) -> Result<()> { ... }
    fn by_ref(&mut self) -> &mut Self
    where
        Self: Sized,
    { ... }
}

Common Read and Write implementors

Read
├── Stdin
├── File
├── TcpStream
├── process::ChildStdout
├── process::ChildStderr
├── BufRead
        ├── BufReader<R>
        ├── Cursor<&[u8]>
        ├── StdinLock
        ├── &[u8]


Write
├── Stdout
├── Stderr
├── File
├── TcpStream
├── Vec<u8>
├── BufWriter<W>
├── process::ChildStdin
// Vec<u8> impl `Write`
// String does not impl `Write`
// to convert to String, use From
String::from_utf8(vec: Vec<u8>);
String::from_utf8_lossy(vec);

Seek trait

pub trait Seek {
    // required
    fn seek(&mut self, pos: SeekFrom) -> Result<u64>;
    // provided
    fn rewind(&mut self) -> Result<()> { ... }
    fn stream_len(&mut self) -> Result<u64> { ... }
    fn stream_position(&mut self) -> Result<u64> { ... }
}
// SeekFrom is an enum
pub enum SeekFrom {
    Start(u64), // sets the offset to the provided no. of bytes
    End(i64),   // sets the offset to the size of this obj, plus the num.
    Current(i64), // sets the offset to the current position plus the num.
}

// seeking within a file is slow
let mut f = File::open("foo.txt")?;
// move the cursor 42 bytes from the start of the file
f.seek(SeekFrom::Start(42))?;

Stdin/Stdout/Stderr

// io::stdin().lock() does not work
// because the lock holds a ref to the Stdin value
// Stdin value must be stored that it lives long enough
let stdin = io::stdin();
let lines = stdin.lock().lines();   // ok