このページでは Rust でパス操作時や OS コマンド実行時などに使われる文字列型を理解するために OsString
と &OsStr
についてまとめる。
String と OsString の違い
OsString
も String
と同じように Vec<u8>
を持つ構造体だが、String
と違い UTF-8 として不正なバイト列も許容している点が違う。
たとえば OS の扱う「ファイル名」や「環境変数」や「コマンドライン引数」など、外部から受け取った文字列は UTF-8 である保証がない。
実際に Linux では UTF-8 ではない文字を含むファイル名を作ることが可能だし、Windows のファイルシステムは UTF-16 だったりする。
これらの文字列をなかば強引に文字列として扱うために、String
とは別に OsString
が用意されている。
OsString を見かけるタイミング
順番は次のページと前後するが、ファイル入出力の処理を書いているときに PathBuf
や DirEntry
からファイル名やパスなどを取り出そうとしても、String
を得ることはできない。
OsString と &OsStr の違い
&
がついていることから察しはつくが、OsString
と &OsStr
の関係は String
と &str
の関係と同じだ。
相互変換も同じような感じで行える。
let os_str: &OsStr = &os_string;
let os_string: OsString = os_str.to_os_string();
Vec<u8> から OsString への変換
OsString
は Vec<u8>
を強引に文字列として扱うための型なので、String
と違い Vec<u8>
からの変換は失敗しない。
OsString::from_vec
は Vec<u8>
から OsString
を生成する。
↓ は あ
と コピーライトマーク、それに String
には変換できなかった 2 つのバイト列が変換できる例だ。
let ns: Vec<u8> = vec![0xe3, 0x81, 0x82];
let os_string: OsString = OsString::from_vec(ns);
// "あ"
let ns: Vec<u8> = vec![0xc2, 0xa9];
let os_string: OsString = OsString::from_vec(ns);
// "©"
let ns: Vec<u8> = vec![0x80];
let os_string: OsString = OsString::from_vec(ns);
// "\x80"
let ns: Vec<u8> = vec![0xf4, 0x90, 0x80, 0x80];
let os_string: OsString = OsString::from_vec(ns);
// "\xF4\x90\x80\x80"
UTF-8 として不正な Vec<u8>
でも変換に失敗しないことがわかる。
( Unicode の文字に対応しないので印字は結局できないが )
図に書き足しておく。
String と OsString の相互変換
どちらも Vec<u8>
を擁する構造体だが、ここまでで理解した通り String
の方が制約が厳しい。
つまり String
→ OsString
の変換は絶対成功するが、
let os_string: OsString = string.into();
OsString
→ String
の変換は失敗する可能性がある。
let string: Result<String, OsString> = os_string.into_string();
String と OsString どちらを使うか
Rust で外部コマンドを実行する ↓ のコードは、ここまでの知識で理解できる。
let output = Command::new("date").output().unwrap();
let line: String = String::from_utf8(output.stdout).expect("Not UTF-8");
from_utf8
の引数にした output.stdout
を変数にして型注釈もつけると ↓ になる。
let output = Command::new("date").output().unwrap();
let stdout: Vec<u8> = output.stdout;
let line: String = String::from_utf8(stdout).expect("Not UTF-8");
stdout: Vec<u8>
が UTF-8 バイト列として有効かわからないため、String
にしたいなら expect
で Result
を剥がす必要がある。
String
ではなく OsString
にしたいなら ↓ のようにシンプルになる。
let output = Command::new("date").output().unwrap();
let line: OsString = OsString::from_vec(output.stdout);
OsString
にする方がこのコードはシンプルになるが、そのあとの Rust の処理を全て OsString
で通すのは無理があるので、現実的にはなんとかして String
にすることになるだろう。
整理
-
OsString
もString
と同じくVec<u8>
を持つ構造体 -
OsString
と&OsStr
の相互変換は容易 -
OsString
はString
と違い UTF-8 として不正なVec<u8>
でも強引に保持できる -
String
の方が制限が厳しいので、OsString
→String
の変換は失敗する可能性がある
参考