🐡

Rustの基本的なFile,Dir操作メモ

2024/04/05に公開


Rustの基本的なファイル・ディレクトリ操作について調べたので備忘として残しておきます。

パス操作

サンプルコード

use anyhow::{Context, Result};
use std::path::{Path, PathBuf};
use std::ffi::{OsStr, OsString};

fn main() -> Result<()> {
    // get &Path
    let dir: &Path = Path::new("./rsc/");
    let file1: &Path = Path::new("./rsc/file_foo.txt");

    // get PathBuf
    let mut file2: PathBuf = PathBuf::from(dir);
    file2.push("file_bar");
    file2.set_extension("txt");
    assert_eq!(file2, dir.join("file_bar.txt"));

    // convert &Path from/to PathBuf
    let _pathbuf: PathBuf = file1.to_path_buf();
    let _path: &Path = file2.as_path();
    let _path: &Path = &*file2;

    // check path status
    assert!(dir.try_exists()?); // error if not accessable
    assert!(dir.is_dir());
    assert!(file1.is_file());
    assert!(!dir.is_absolute());
    assert!(dir.is_relative());

    // get path component
    assert_eq!(file1.parent().unwrap(), dir);
    assert_eq!(file1.extension().unwrap(), OsStr::new("txt"));
    assert_eq!(file1.file_name().unwrap(), OsStr::new("file_foo.txt"));
    assert_eq!(dir.file_name().unwrap(), OsStr::new("rsc"));

    // get dir contents
    for result in dir.read_dir().context("read dir error")? {
        if let Ok(entry) = result {
            let _pathbuf: PathBuf = entry.path();
            let name: OsString = entry.file_name();
            println!("{}", name.to_str().unwrap());
        }
    }
    Ok(())
}

メモ

  • Pathstrのようなもの
    • &Path&strPathBufStringに相当する
    • &OsStrOsStringも似たようなもの
  • ディレクトリの内容取得はパスからもできる
    • std::fs::read_dirも同じことができる
  • try_exists等はTOCTOUが発生しうることを念頭に置いておく

ファイル読み書き

サンプルコード

use anyhow::{Context, Result};
use std::fs::File;
use std::path::{Path, PathBuf};

fn main() -> Result<()> {
    let file1: &Path = Path::new("./rsc/file_foo.txt");
    let file2: &Path = Path::new("./rsc/file_bar.txt");
    let file3: &Path = Path::new("./rsc/file_baz.txt");

    let (reader, writer) = get_text_rw(file1, WriteMode::Replace);
    let body1 = ["abc", "def", "123"].iter().map(|&x| x.to_string()).collect::<Vec<String>>();
    // efficient file io
    writer.write_all_as_lines(body1)?;
    println!("{}", reader.read_all_as_lines()?); // abc\ndef\n123

    let (reader, writer) = get_text_rw(file2, WriteMode::Append);
    let body2 = "append_bar".to_string();
    // simple file io
    writer.write_all_as_whole(body2)?;
    println!("{}", reader.read_all_as_whole()?); // ... append_bar

    let (reader, writer) = get_text_rw(file3, WriteMode::AsNew);
    let body3 = ["t", "x", "t"].iter().map(|&x| x.to_string()).collect::<Vec<String>>();
    writer.write_all_as_lines(body3)?;     // error if file3 exists
    println!("{}", reader.read_all_as_lines()?); // t\nx\nt

    Ok(())
}

fn get_text_rw(path: &Path, mode: WriteMode) -> (TextFileReader, TextFileWriter) {
    (TextFileReader::new(path), TextFileWriter::new(path, mode))
}

struct TextFileReader {
    path: PathBuf,
}
impl TextFileReader {
    fn new(path: &Path) -> Self {
        Self { path: path.to_path_buf() }
    }
    fn open_file(&self) -> Result<File> {
        File::open(self.path.as_path()).context("cannot open path as read mode") // read only
    }
    fn read_all_as_lines(&self) -> Result<String> {
        use std::io::{BufRead, BufReader};
        let buff_reader: BufReader<File> = BufReader::new(self.open_file()?);

        let mut lines = Vec::new();
        for line in buff_reader.lines() {
            // if process here, can save memory
            lines.push(line.context("read error")?);
        }
        Ok(lines.join("\n"))
    }
    fn read_all_as_whole(&self) -> Result<String> {
        use std::io::Read;
        let mut body = String::new();
        self.open_file()?.read_to_string(&mut body).context("read error")?;
        Ok(body)
    }
}

enum WriteMode {
    Replace,
    AsNew,
    Append,
}
struct TextFileWriter {
    path: PathBuf,
    mode: WriteMode,
}
impl TextFileWriter {
    fn new(path: &Path, mode: WriteMode) -> Self {
        Self { path: path.to_path_buf(), mode }
    }
    fn open_file(&self) -> Result<File> {
        use std::fs::OpenOptions;
        match self.mode {
            WriteMode::Replace => File::create(self.path.as_path()).context("cannot open path as write mode"), // write only (write,create,truncate)
            WriteMode::AsNew => OpenOptions::new().create_new(true).write(true).open(self.path.as_path()).context("cannot open path as new file"),
            WriteMode::Append => OpenOptions::new().append(true).create(true).open(self.path.as_path()).context("cannot open path as append mode"),
        }
    }
    fn write_all_as_lines(&self, body: Vec<String>) -> Result<()> {
        use std::io::{BufWriter, Write};
        let mut buff_writer: BufWriter<File> = BufWriter::new(self.open_file()?);

        for line in body.into_iter().map(|s| s + "\n") {
            buff_writer.write(line.as_bytes()).context("write error")?;
        }
        buff_writer.flush().context("flush error")?;
        Ok(())
    }
    fn write_all_as_whole(&self, body: String) -> Result<()> {
        use std::io::Write;
        self.open_file()?.write_all((body + "\n").as_bytes()).context("write error")?;
        Ok(())
    }
}

メモ

  • ファイル全体を一度に読み込むとメモリ消費が激しいためBufReaderの使用が好ましい
  • BufReader,BufWriterは同ファイルに対する小さい値の繰り返し読み,書きで性能向上しやすい
  • BufWriterは明示的に最後にflushするのが望ましい
    • BufWriterドロップ時に自動flushされるが、ドロップ時のエラー発生は無視され書き込まれない
  • ファイルに追記するためにはstd::fs::OpenOptionsを用いる必要がある

ファイル,ディレクトリ操作

サンプルコード

use anyhow::Result;
use std::path::Path;
use std::fs::*;

fn main() -> Result<()> {
    let file3: &Path = Path::new("./rsc/file_baz.txt");

    create_dir(Path::new("./rsc/foo/"))?;
    create_dir_all(Path::new("./rsc/foo/bar/baz"))?; // create dir with all parent dirs
    copy(file3, Path::new("./rsc/foo/bar/baz/qux.txt"))?;
    rename(Path::new("./rsc/foo/bar/baz/qux.txt"), Path::new("./rsc/foo/bar/baz/quux.txt"))?;
    remove_dir_all(Path::new("./rsc/foo/"))?;
    remove_file(file3)?;

    Ok(())
}

メモ

  • symlink向けの機能もある(詳細は公式参照)

参考文献

Discussion