学生の備忘録なブログ

日々のことを忘れないためのブログです。一日一成果物も目標。技術系はQiitaにあげるように変更しました。

ソフトウェアシステム論 (exaを読む 実践編)

要旨

ソフトウェアシステム論 (exaを読む 環境構築編)の続き

メモ

講義を忘れないためにとりあえずメモ

Result<T,E>型

Result<,>ってなに * _はなんでもいいときに使う?

Result<T,E>はいつ使う * エラー処理のための戻り値につかう。 * Rustは例外がない。 * Result<>かpanic!を使う。 * 回復可能なResult<> * 終了させるpanic!

参考 Resultで回復可能なエラー

?演算子(?後置演算子)

  • エラー委譲の省略形の書き方 実行時、エラーを想定して予めmatchで条件分岐などを書きたくない場合に用いる。 失敗すると、単に関数の実行から抜ける。

PathBuf

cloneをなぜするのか

他の言語のdeepコピーに相当する。 同じオブジェクトに別の名前を束縛するのではなく、新たにコピーを作る。

今回exaでcloneをする理由は所有権かライフタイムの問題からだと思われる。 参考

exaデバッグ実行

main.rs

11行目

configure_logger();
  • logを取るメソッド

13行目

let args: Vec<OsString> = args_os().skip(1).collect();
  • args:OsStringを持つ配列
  • args_os().skip(1)がイテレータを返す
  • collect()がイテレータを配列にまとめる args_os().skip(1).collect():コマンド実行の引数をコマンドそれ自体をスキップしてそれ以降の引数を配列'args'として得る。

14行目

match Exa::from_args(args.iter(), &mut stdout()) {
  • main.rs2行目にuse exa::Exaとあり、外部ライブラリファイルexa.rsのExaメソッドをインポートしている
  • match式でfrom_argsメソッドに対し引数args.iter(),stdout()を渡す
  • args.iter()は配列argsからイテレータを作る
  • stdout()は標準出力へのハンドラを作る

from_args()

exa.rs 86行目

    pub fn from_args<I>(args: I, writer: &'w mut W) -> Result<Exa<'args, 'w, W>, Misfire>
    where I: Iterator<Item=&'args OsString> {
        Options::parse(args, &LiveVars).map(move |(options, mut args)| {
            debug!("Dir action from arguments: {:#?}", options.dir_action);
            debug!("Filter from arguments: {:#?}", options.filter);
            debug!("View from arguments: {:#?}", options.view.mode);

            // List the current directory by default, like ls.
            // This has to be done here, otherwise git_options won’t see it.
            if args.is_empty() {
                args = vec![ OsStr::new(".") ];
            }

            let git = git_options(&options, &args);
            let ignore = ignore_cache(&options);
            Exa { options, writer, args, git, ignore } 
        })
    }

これは途中でmap()の中に入るので注意

  • parse()イテレータを解析してOptions構造体とfree filenameの配列を作成する
  • LiveVarsThe “real” environment variables type.,just calling "var_os"環境変数?よくわからない。
  • moveするのはRustの変数のライフタイムが遅延評価のmapのクロージャの中でwriterを使うから?
    • ためしにmoveをなくしてコンパイルしてみたところ、"help: クロージャwriter (および他の参照変数) の所有権を強制的に取得するには、move キーワードを使用します。"とエラーがでた。
  • 戻り値は構造体Exa

exa.rs 93行目

         if args.is_empty() {
                args = vec![ OsStr::new(".") ];
            }

15行目

Ok(mut exa) 
  • match式が問題なく実行された場合の処理が書かれている

16行目

match exa.run(){

exa.run()

  • 実際にファイルパスを取得する

exa.rs 115行目 run()

        for file_path in &self.args {
            match File::from_args(PathBuf::from(file_path), None, None) {
                Err(e) => {
                    exit_status = 2;
                    writeln!(stderr(), "{:?}: {}", file_path, e)?;
                }
                Ok(f) => {
                    if f.points_to_directory() && !self.options.dir_action.treat_dirs_as_files() {
                        match f.to_dir() {
                            Ok(d) => dirs.push(d),
                            Err(e) => writeln!(stderr(), "{:?}: {}", file_path, e)?,
                        }
                    } else {
                        files.push(f);
                    }
                }
            }
        }

  • exa.run()が成功すればexit
  • 失敗したらエラーを吐いて終わり

  • File::from_args()の戻り値 File { path, parent_dir, metadata, ext, name, is_all_all }

  • from_args()で親ディレクトリやファイル名を変数pathから得る
  • Rustの&strとStirngの関係がPathとPathBufの関係と対応しているらしい。

An owned, mutable path (akin to String). This type provides methods like push and set_extension that mutate the path in place. It also implements Deref to Path, meaning that all methods on Path slices are available on PathBuf values as well. More details about the overall approach can be found in the module documentation.

#![allow(unused)]
fn main() {
use std::path::PathBuf;

let path = PathBuf::from(r"C:\windows\system32.dll");
println!("{:?}",path)
}
>>> "C:\\windows\\system32.dll"
  • &strは文字列スライスで参照のみ
  • Stringは文字列のベクタ
  • Pathのドキュメントを見るとpush,popはなかった。PathBufの方にあった。
  • Pathを追加したり、削ったりという変更を加えるときはPathBufに入れるようだ。

filename()

file.rs 105行目

/// A file’s name is derived from its string. This needs to handle directories
    /// such as `/` or `..`, which have no `file_name` component. So instead, just
    /// use the last component as the name.
    pub fn filename(path: &Path) -> String {
        if let Some(back) = path.components().next_back() {
            back.as_os_str().to_string_lossy().to_string()
        }
        else {
            // use the path as fallback
            error!("Path {:?} has no last component", path);
            path.display().to_string()
        }
    }
  • pathを正規化して後ろから(右から)ファイル名を返すメソッド
  • components()配列じゃないものをイテレータにする
  • next_bask()イテレータの後ろから要素を得る
  • as_os_str() PashをOsStr形式に変換しファイル名に切り分ける
  • to_string_lossy無効なUnicodeが入っていたら、U+FFFD REPLACEMENT CHARACTER (�)に変換する 戻り値はCow型(A clone-on-write smart pointer.スマートポインタってなに?Rustのポインタ型)
  • to_string()String型に変換する

pathをクローンするのは、色々他にも作業があるから。

file.rs 35行目 * なぜpathをクローンするの?

ファイル名が抽出されていても、ファイルの絶対位置を調べたり (コンパイルされたファイルの検索など)、元のパスを使ったり (シンボリックリンクに続く) する操作があるため、パスを保持しておく必要があります。

read_dir()は何をして何を返すのか

dirs.rs 34行目

    pub fn read_dir(path: PathBuf) -> IOResult<Dir> {
        info!("Reading directory {:?}", &path);

        let contents = fs::read_dir(&path)?
            .map(|result| result.map(|entry| entry.path()))
            .collect::<Result<_, _>>()?;
        println!("{:?}", contents); 
        Ok(Dir { contents, path })
    }
  • read_dir()は何を返しているの? read_dir()の戻り値は以下のような感じで変化する read_dir()->Result<ReadDir> read_dir()?->ReadDirイテレータ

ReadDirResult<DirEntry>を要素に持つイテレータ

  • 2重mapはなにをしているの? 1回目map イテレータReadDirの各要素Result<DirEntry>に対して 2回目map Resul<DirEntry>の要素DirEntryに対してpath() entry.path()はDirEntryをpath型に変換する処理

  • 例 read_dir()が指定されたディレクトリの要素をもつイテレータを返すコード

#![allow(unused)]
use std::fs;

fn main() -> std::io::Result<()> {
    for entry in fs::read_dir(".")? {
        let dir = entry?;
        println!("{:?}", dir);
        println!("{:?}", dir.path());
    }
    Ok(())
}
DirEntry("./.bash_logout")
"./.bash_logout"

Playgrougndで実行する%5D%0Ause%20std%3A%3Afs%3B%0A%0Afn%20main()%20-%3E%20std%3A%3Aio%3A%3AResult%3C()%3E%20%7B%0A%20%20%20%20for%20entry%20in%20fs%3A%3Aread_dir(%22.%22)%3F%20%7B%0A%20%20%20%20%20%20%20%20let%20dir%20%3D%20entry%3F%3B%0A%20%20%20%20%20%20%20%20println!(%22%7B%3A%3F%7D%22%2C%20dir.path())%3B%0A%20%20%20%20%7D%0A%20%20%20%20Ok(())%0A%7D&edition=2018)

  • contests変数に束縛するときの処理が分かりづらい。
    • DirEntry公式ドキュメントがわかりやすかった。read_dir()はReadDir型を返すらしいが、DirEntryの例文がよりわかりやすかった。2重mapがないため。

run()の戻り値

exa.rs 144行目

        self.print_dirs(dirs, no_files, is_only_dir, exit_status)

17行目

                Ok(exit_status) => exit(exit_status),
  • exa.run()が成功したのでexitする
  • exa.run()の戻り値はfilename()

参考文献

PathBuf関係

Rustのファイルシステムコールのコーディング例

map move関係

c言語でls