Rustでファイルの入出力

この記事は[Wanoアドベントカレンダー](https://qiita.com/advent-calendar/2018/wano-group)の3日目の記事です。

## TL;DR

* どんなAPIがあるの？
  * [std::fs](https://doc.rust-lang.org/std/fs/index.html)
     * ファイルオープンやディレクトリ操作
  * [std::io](https://doc.rust-lang.org/std/io/index.html)
     * IO関連のtrait(`Read`/`Write`等)や標準入出力
* 注意事項
  * 標準の入出力APIのうち最もレイヤが低いAPIはバッファリングさえも除かれている
     * この事を把握せずに自分で頑張ろうとすると性能が出ない事が多い
     * BufReader/BufWriterを使えばバッファリングされるので基本はこれを使う
* ユースケース別やり方
  * ファイルの内容全体を文字列として一気に読み込み
     * `std::fs::read_to_string`を使う
     * `std::io::Read` traitの`read_to_string`メソッドを使う
  * 1行ずつ文字列を読み込みたい
     * `std::io::BufRead` traitの`read_line`メソッドか`lines`メソッドを使う
     * `BufRead` traitは`std::io::BufReader`型が実装している
  * ファイルの内容全体をバイト列として一気に読み込み
     * `std::io::Read` traitの`read_to_end`メソッドを使う
  * バイト列をバッファに読み込みながら処理したい
     * `std::io::BufReader`を使う
     * `BufReader`相当の事を自分でやるなら`std::io::Read` traitの`read`メソッドを使う
  * 面倒な事は一切しないで1バイトずつ処理したい
     * `std::io::Read` traitの`bytes`メソッドを使う
  * 文字列/バイト列を一度に書き出す
     * 自分で`String`型や`Vec<u8>`型の変数に溜めてから`std::io::Write` traitの`write_all`メソッドで一度に書き出す
     * 文字列の場合は`write!`もしくは`writeln!`マクロが`println!`の様に使う事ができる。
  * 文字列/バイト列を少しずつ複数回に分けて書き出す
     * `std::io::BufWriter`を使う事以外は一度に書き出す場合と同じ

## どんなAPIがあるの？

* [std::io](https://doc.rust-lang.org/std/io/index.html)
* [std::fs](https://doc.rust-lang.org/std/fs/index.html)

この2つが標準の同期APIです。

`std::io`には[Read](https://doc.rust-lang.org/std/io/trait.Read.html)/[Write](https://doc.rust-lang.org/std/io/trait.Write.html)/[Seek](https://doc.rust-lang.org/std/io/trait.Seek.html)/[BufRead](https://doc.rust-lang.org/std/io/trait.BufRead.html)といった入出力の為のtraitや標準入出力の型([Stdin](https://doc.rust-lang.org/std/io/struct.Stdin.html)/[Stdout](https://doc.rust-lang.org/std/io/struct.Stdout.html)/[Stderr](https://doc.rust-lang.org/std/io/struct.Stderr.html))等があります。

`std::fs`にはFile操作の為の[File](https://doc.rust-lang.org/std/fs/struct.File.html)型やディレクトリ操作の為の関数や型等があります。

## 注意事項

`Read` traitや `Write` traitの最もプリミティブな操作である`read`や`write`メソッドは多くの場合に実装が低レイヤー過ぎる為に、他の言語と同様の感覚で使用するとパフォーマンスが出ないという問題が起こります。  
`Read`や`Write` traitを実装している型(例えば`File`や`Stdin`や`Stdout`)を、`BufReader`や`BufWriter`型で包んであげる事で`read`や`write`でバッファリングが行われるようになり、他の言語と同様のパフォーマンスを出す事が出来るようになります。

## ユースケース別やり方

### ファイルの内容全体を文字列として一気に読み込み

Rust v1.26.0以降に追加された[std::fs::read_to_string](https://doc.rust-lang.org/std/fs/fn.read_to_string.html)を使用すると簡単です。

~~~~rust
use std::fs;

fn main() -> Result<(), Box<std::error::Error>> {
    let content = fs::read_to_string("path/to/file")?;
    println!("{}", content);
    Ok(())
}
~~~~

他には`Read` traitの`read_to_string`メソッドを使ったやり方もあります。

~~~~rust
use std::io::{self, Read};

fn get_content<R: Read>(r: &mut R) -> io::Result<String> {
    let mut buf = String::new();
    let _ = r.read_to_string(&mut buf)?;
    Ok(buf)
}

fn main() -> Result<(), Box<std::error::Error>> {
    let content = get_content(&mut io::stdin())?;
    println!("{}", content);
    Ok(())
}
~~~~

`read_to_string`は引数に読み込んだデータを保存するバッファを要求するので、1つのString変数を使い回すことでメモリアロケーション回数を減らす事も出来ます。

### 1行ずつ文字列を読み込みたい

`BufReader`が実装している`BufRead` traitの`lines`メソッドを使って`Iterator`として処理します。

~~~~rust
use std::fs::File;
use std::io::{self, BufRead, BufReader};

fn main() -> Result<(), Box<std::error::Error>> {
    for result in BufReader::new(File::open("path/to/file")?).lines() {
        let l = result?;
        println!("{}", l);
    }
    Ok(())
}
~~~~

`BufReader`が実装している`BufRead` traitの`read_line`メソッドを使って細かく制御していく事も出来ます。

~~~~rust
use std::io::{self, BufRead, BufReader};

fn main() -> Result<(), Box<std::error::Error>> {
    let mut reader = BufReader::new(io::stdin());
    let mut buf = String::new();

    while reader.read_line(&mut buf)? > 0 {
        println!("{}", buf);
        buf.clear();
    }

    Ok(())
}
~~~~

`read_line`を用いると改行文字も`buf`に入るので注意して下さい。

### ファイルの内容全体をバイト列として一気に読み込み

`Read` traitの`read_to_end`メソッドを使います。

~~~~rust
use std::fs::File;
use std::io::Read;

fn main() -> Result<(), Box<std::error::Error>> {
    let mut file = File::open("path/to/file")?;
    let mut buf = Vec::new();
    let _ = file.read_to_end(&mut buf)?;
    println!("{:?}", buf);
    Ok(())
}
~~~~

### バイト列をバッファに読み込みながら処理したい

`BufReader`を使って読み込みます。

~~~~rust
use std::fs::File;
use std::io::{Read, BufReader};

fn main() -> Result<(), Box<std::error::Error>> {
    let mut reader = BufReader::new(File::open("path/to/file")?);
    let mut buf = [0; 4];

    loop {
        match reader.read(&mut buf)? {
            0 => break,
            n => {
                let buf = &buf[..n];
                println!("{:?}", buf);
            }
        }
    }

    Ok(())
}
~~~~

何らかの理由でBufReaderのバッファリングが邪魔な場合は`Read` traitの`read`メソッドを自分で呼び出して読み込んでいきます。

~~~~rust
use std::fs::File;
use std::io::Read;

fn main() -> Result<(), Box<std::error::Error>> {
    let mut file = File::open("path/to/file")?;
    let mut buf = [0; 4];

    loop {
        match file.read(&mut buf)? {
            0 => break,
            n => {
                let buf = &buf[..n];
                println!("{:?}", buf);
            }
        }
    }

    Ok(())
}
~~~~

### 面倒な事は一切しないで1バイトずつ処理したい

`BufReader`を通して`Read` traitの`bytes`メソッドを呼びます。

~~~~rust
use std::fs::File;
use std::io::{Read, BufReader};

fn main() -> Result<(), Box<std::error::Error>> {
    for result in BufReader::new(File::open("path/to/file")?).bytes() {
        let byte = result?;
        println!("{:x}", byte);
    }

    Ok(())
}
~~~~

### 文字列/バイト列を一度に書き出す

自分でバッファリングして`Write` traitの`write_all`メソッドを使って書き出します。  
書き込み可能なファイルハンドルは`File`型の`create`メソッドで作成します。

~~~~rust
use std::fs::File;
use std::io::{self, Read, Write, BufReader};

fn main() -> Result<(), Box<std::error::Error>> {
    let mut file = File::create("path/to/file")?;
    let buf = BufReader::new(io::stdin()).bytes().collect::<io::Result<Vec<u8>>>()?;
    file.write_all(&buf)?;
    file.flush()?;
    Ok(())
}
~~~~

バイト列ではなく文字列の場合は`write!`または`writeln!`マクロを使う事で`println!`と同じ様に書きだす事ができます。

~~~~rust
use std::fs::File;
use std::io::{self, BufRead, Write, BufReader};

fn main() -> Result<(), Box<std::error::Error>> {
    let mut file = File::create("path/to/file")?;
    let buf = BufReader::new(io::stdin()).lines().collect::<io::Result<Vec<String>>>()?.join("\n");
    write!(file, "{}", buf)?;
    file.flush()?;
    Ok(())
}
~~~~

### 文字列/バイト列を少しずつ複数回に分けて書き出す

`BufWriter`を使う事以外は一度に書き出す場合と基本は同じです。

~~~~rust
use std::fs::File;
use std::io::{self, Read, Write, BufReader, BufWriter};

fn main() -> Result<(), Box<std::error::Error>> {
    let mut writer = BufWriter::new(File::create("path/to/file")?);
    for result in BufReader::new(io::stdin()).bytes() {
        let byte = result?;
        writer.write_all(&[byte])?;
    }
    file.flush()?;
    Ok(())
}
~~~~