RustのFile::openにおけるパスの指定について

Rustのモジュールstd::fs::File::openの仕様について教えて頂いたことを,備忘録として残します.

困っていたこと

プロジェクトのルートディレクトリにhello.htmlというファイルを置いて,src/main.rsから読む.ソースコードを以下に示す.

use std::fs::File;

fn main() {
    let handle = File::open("../hello.html").unwrap();
}

main.rsから見てhtmlファイルは1つ上のディレクトリに存在するのでこう書けると思ったのだが,No such file or directoryというエラーが出てプログラムはパニックした.

原因

cargo runでプログラムを実行すると,cargo runした場所がCWD(カレントワーキングディレクトリ)になるという仕様がある.私はルートディレクトリでcargo runしていたので,指定した相対パスは適切ではなかった(プロジェクトのディレクトリの外に行ってしまう).
したがって,File::openの引数を"hello.html"に直せばよい.

より良い(?)方法

Cargoは種々の環境変数をセットしてくれ,プログラムで使用できる(詳しくはここ).その環境変数の中のCARGO_MANIFEST_DIRを使って絶対パスを指定する.

let filepath = format!("{}/hello.html", std::env::var("CARGO_MANIFEST_DIR").unwrap());
// 展開すると "/home/user_name/hello/hello.html"のようになる
let handle = File::open(filepath).unwrap();

絶対パスで指定すれば,どこで実行しても問題なく動作する.

さらなる改良

Rustには,ファイルパスに特化した文字列型を提供するstd::path::{Path, PathBuf}がある.ディレクトリの区切り文字を環境によって変更してくれる,などの嬉しい機能が実装されているらしい.
CARGO_MANIFEST_DIRを利用して取得した文字列(Stringだと思うけど,自信はない...)をfromで変換してやる.

let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
let mut filepath = PathBuf::from(manifest_dir);
filepath.push("hello.html");
let handle = File::open(&filepath)?;