Chapter 04

OsString と &OsStr

ほげさん
ほげさん
2023.03.01に更新

このページでは Rust でパス操作時や OS コマンド実行時などに使われる文字列型を理解するために OsString&OsStr についてまとめる。

String と OsString の違い

OsStringString と同じように Vec<u8> を持つ構造体だが、String と違い UTF-8 として不正なバイト列も許容している点が違う。

たとえば OS の扱う「ファイル名」や「環境変数」や「コマンドライン引数」など、外部から受け取った文字列は UTF-8 である保証がない。
実際に Linux では UTF-8 ではない文字を含むファイル名を作ることが可能だし、Windows のファイルシステムは UTF-16 だったりする。

これらの文字列をなかば強引に文字列として扱うために、String とは別に OsString が用意されている。

OsString を見かけるタイミング

順番は次のページと前後するが、ファイル入出力の処理を書いているときに PathBufDirEntry からファイル名やパスなどを取り出そうとしても、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 への変換

OsStringVec<u8> を強引に文字列として扱うための型なので、String と違い Vec<u8> からの変換は失敗しない。

OsString::from_vecVec<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 の方が制約が厳しい。

つまり StringOsString の変換は絶対成功するが、

let os_string: OsString = string.into();

OsStringString の変換は失敗する可能性がある。

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 にしたいなら expectResult を剥がす必要がある。

String ではなく OsString にしたいなら ↓ のようにシンプルになる。

let output = Command::new("date").output().unwrap();
let line: OsString = OsString::from_vec(output.stdout);

OsString にする方がこのコードはシンプルになるが、そのあとの Rust の処理を全て OsString で通すのは無理があるので、現実的にはなんとかして String にすることになるだろう。

整理

  • OsStringString と同じく Vec<u8> を持つ構造体
  • OsString&OsStr の相互変換は容易
  • OsStringString と違い UTF-8 として不正な Vec<u8> でも強引に保持できる
  • String の方が制限が厳しいので、OsStringString の変換は失敗する可能性がある

参考

https://runebook.dev/ja/docs/rust/std/ffi/struct.osstring