Rust入門

【Rust入門】ファイル入出力の基本を分かりやすく解説

【Rust入門】ファイル入出力の基本を分かりやすく解説
naoki-hn

Rust のファイル入出力の基本について解説します。

Rust の入出力

プログラミング言語において、ファイル入出力は非常に基本的で重要な処理です。Rust の入出力は、ReadBufReadWrite というトレイトを中心に構築されています。入出力という広い観点で見ると「ファイル」「標準入出力」「ネットワーク」など様々な対象がありますが、Rust では、いずれもこれらのトレイトを通じて扱うことができます。

この記事では、トレイトの細かなところまでは踏み込みませんが、テキストファイルの入出力を通じて Rust でのファイル入出力の基本を紹介します。

トレイトの基本については以下を参考にしてください。

トレイトの基本を分かりやすく解説

テキストファイルの入出力

テキストファイルの読み込み

ファイルを開いて全てのテキストを読み込む read_to_string

テキストファイルを開いて内容を読み込むには、read_to_string メソッドを使用します。

use std::fs;
use std::io::{self};

fn read_file(filepath: &str) -> io::Result<()> {
    // ファイルパスのデータを String 型で取得する
    let contents = fs::read_to_string(filepath)?;
    // 読み込んだ文字列を表示する
    println!("{}", contents);

    Ok(())
}

fn main() {
    // ファイルパスは任意のパスに変更してください
    let filepath = r"D:\RustProject\rust-tech-sample-source\rust-basic\file_input_output\examples\input_example.txt";

    // ファイル読み込み関数を呼び出し、エラーが発生した場合はエラーを表示する
    if let Err(e) = read_file(filepath) {
        println!("Error: {}", e);
    }
}
【実行結果】※ input_example.txt の内容
Rust Programming
ファイル入出力の基本
テキストファイルの入出力

use std::fs;」とすることで、標準ライブラリ std 内の fs モジュールを使用します。read_to_stringfs モジュール内の関数で、ファイルパスを渡すことで、ファイルの中身を String 型で読み込むことができます。

この read_to_string 関数の返却値の型は、io::Result<String> 型になります。上記例の read_file 関数では、返却値を io::Result<()> として、? 演算子によりエラーが発生した場合や呼び出し元に移譲しています。

エラーの場合は、呼び出し元の main 関数内で if let により Err を補足してエラーを表示するようにしています。より丁寧にエラー処理をする場合は、match による処理ももちろん可能です。エラー処理の基本が分からない方は「エラー処理の基本を分かりやすく解説」も参考にしてください。

ファイルを開いてから読み込む

read_to_string 関数は、上記のようにファイルパスを指定して簡単にテキストファイルを読み込むことができますが、以下の例のように fs::File::open によりファイルを開いてから、読込先の String を渡して読み込むことも可能です。

use std::fs;
use std::io::{self, Read};

fn read_file(filepath: &str) -> io::Result<()> {
    let mut file = fs::File::open(filepath)?;
    let mut contents = String::new();

    // ファイルの内容を読み込み
    file.read_to_string(&mut contents)?;
    // 読み込んだ文字列を表示する
    println!("{contents}");

    Ok(())
}

この時、Read トレイトを use 指定する必要がある点に注意してください。先ほどまで使用していた fs::read_to_stringfs モジュール内に定義された関数でした。一方で、上記例は File 型にトレイトのメソッドとして定義した read_to_string を使用しているため、似ているように見えて全くの別物です。

トレイトで定義されるメソッドを使用する際には、use でトレイトを読み込む必要があるため、上記の例では Read トレイトの読み込みが必要となるわけです。

1行ずつ読み込む

ファイルを 1行ずつ読み込む場合には、BufReader により順次処理をすることが可能です。

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

fn read_lines(filepath: &str) -> io::Result<()> {
    let file = File::open(filepath)?;
    let reader = BufReader::new(file);

    // ファイルを1行ずつ読み込む
    for line in reader.lines() {
        // line は Result<String, Error> なので値を取り出す
        let line = line?;
        println!("{} :文字数({})", line, line.chars().count());
    }

    Ok(())
}

fn main() {
    // ファイルパスは任意のパスに変更してください
    let filepath = r"D:\RustProject\rust-tech-sample-source\rust-basic\file_input_output\examples\input_example.txt";

    // ファイル読み込み関数を呼び出し、エラーが発生した場合はエラーを表示する
    if let Err(e) = read_lines(filepath) {
        println!("Error: {}", e);
    }
}
【実行結果】
Rust Programming :文字数(16)
ファイル入出力の基本 :文字数(10)
テキストファイルの入出力 :文字数(12)

上記例では、まず File::open によりファイルを開き、BufReadernew 関数に渡すことで reader を生成します。reader.lines() により各行のデータを順に取得することができるため、for 文で line に値を入れつつ 1行ずつ処理をすることが可能です。

なお、BufRead トレイトと BufReader 型を use 指定する必要があるので注意してください。

取り出される line は、Result<String, Error> 型であるため、? 演算子により値を取り出してから使用しています。このようにすることで、1行ずつファイルを読み込み処理をすることができます。

テキストファイルの書き込み

入力は主に read_to_string などのメソッドを用いて行いましたが、書き込みでは write! マクロを使用します。

ファイルを作成して書き込む

ファイルの書き込みを行う場合には、OpenOptions 型を使用します。この型では、「.」でメソッドを連結することでファイルを開く際のオプションを指定することが可能になっています。

use std::fs::OpenOptions;
use std::io::{self, Write};

fn write_new_file(filepath: &str, text: &str) -> io::Result<()> {
    // ファイルを作成して開く
    let mut file = OpenOptions::new()
        .write(true)
        .create_new(true) // ファイルが存在するとエラー
        .open(filepath)?;

    // 文字列を書き込む
    writeln!(file, "{text}")?;

    Ok(())
}

fn main() {
    // ファイルパスは任意のパスに変更してください
    let filepath = r"D:\RustProject\rust-tech-sample-source\rust-basic\file_input_output\examples\output_example.txt";

    // 書き込み文字列を準備
    let write_str = "Rust Programming\nファイル入出力の基本\nテキストファイルの入出力";

    // ファイル書き込み関数を呼び出し、エラーが発生した場合はエラーを表示する
    if let Err(e) = write_new_file(filepath, write_str) {
        println!("Error: {e}");
    }
}

ファイルを書き込む際には「.write(true).create_new(true).open(filepath)」とすることで、指定したファイルパスでファイルを作成して、書き込みモードで開くことができます。この時ファイルが存在する場合は、エラーとなります。

ファイルを書き込む場合には、writeln! マクロを使用します。このマクロは、println! マクロとほとんど同じですが、第1引数に File 型のインスタンスを指定していることとが異なります。なお、改行を含まないように出力したい場合は write! マクロで対応可能です。

また、返却値として Result 型が返ってくる点も println! マクロとは異なるためエラー処理が必要です。上記例では ? 演算子により処理をしています。

ファイルを開いて追記する

ログファイルの出力など、ファイルを追記モードで開いて末尾に文字列を追記したくなることが良くあります。この場合は、以下のように「.append(true)」とすることで対応可能です。

use std::fs::OpenOptions;
use std::io::{self, Write};

fn write_append_file(filepath: &str, text: &str) -> io::Result<()> {
    // ファイルを追記モードで開く
    let mut file = OpenOptions::new()
        .append(true) // ファイルが存在しない場合、エラー
        .open(filepath)?;

    // 文字列を書き込む
    writeln!(file, "{text}")?;

    Ok(())
}

fn main() {
    let filepath = r"D:\RustProject\rust-tech-sample-source\rust-basic\file_input_output\examples\output_example.txt";

    // 追記する文字列を準備
    let append_str = "Rust 文字列の追記";

    // ファイル書き込み関数を呼び出し、エラーが発生した場合はエラーを表示する
    if let Err(e) = write_append_file(filepath, append_str) {
        println!("Error: {e}");
    }
}

この場合、ファイルが存在しない場合はエラーとなるので注意してください。

まとめ

Rust のファイル入出力の基本について解説しました。この記事では、テキストファイルの読み込み・書き込みを中心に紹介しています。

ファイル入出力は、どのようなプログラミング言語においても基本になります。Rust の I/O を理解することで、より安全でパフォーマンスの高いプログラムが書けるようになりましょう。

ABOUT ME
ホッシー
ホッシー
システムエンジニア
はじめまして。当サイトをご覧いただきありがとうございます。
私は製造業のメーカーで、DX推進や業務システムの設計・開発・導入を担当しているシステムエンジニアです。これまでに転職も経験しており、以前は大手電機メーカーでシステム開発に携わっていました。

これまでの業務を通じてさまざまなプログラミング言語や技術に触れてきましたが、その中でもRustの設計思想に惹かれ、この言語についてもっと深く学びたい、そしてその魅力を発信していきたいと思い、このサイトを立ち上げました。

自身の学びを整理しつつ、同じようにRustに興味を持つ方のお役に立てるような情報を発信していければと思っています。どうぞよろしくお願いいたします。

※キャラクターデザイン:ゼイルン様
記事URLをコピーしました