【Rust】OsStr と OsString


OsStr と OsString

  • Rust の文字列は常に有効な Unicode であることが保証されている
  • OSのファイル名やコマンドライン引数、環境変数などは有効な Unicode で表現できる保証がない
    • 多くの Unix システムでは、文字列は非ゼロバイトの任意のシーケンスである(通常はUTF-8として解釈される)
    • 多くの Windows では、文字列はゼロ以外の16ビット値の任意のシーケンスである(通常はUTF-16として解釈される)
  • Unicode で表現できないOSの文字列を扱うために std::ffi::OsStrstd::ffi::OsString がある(strString に対応する)
  • std::ffi::OsStrstd::ffi::OsString は Unix であっても Windows であっても、8ビット値のシーケンスとして格納される(Windows では UTF-8 を拡張したエンコードが行われ、任意の 16 ビット 値が表現できる形でコード化される)
pub struct OsStr {
    inner: Slice,
}
pub struct Slice {
    pub inner: [u8],
}
pub struct OsString {
    inner: Buf,
}
pub struct Buf {
    pub inner: Vec<u8>,
}


&OsStr の生成

文字列から

let os_str: &OsStr = OsStr::new("foo");

OsString から

let os_string: OsString = OsString::from("foo");
let os_str: &OsStr = OsStr::new(&os_string);

バイト配列から

 use std::os::unix::ffi::OsStrExt;
 let source = [0x66, 0x6f, 0x80, 0x6f];
 let os_str: &OsStr = OsStr::from_bytes(&source[..]);


OsString の生成

let mut os_string: OsString = OsString::new();
os_string.push("foo");

OsStringFrom<String> を実装しているため、 文字列から OsString を作成できる。

let s: &str = "foo";
let os_string: OsString = s.into();
let string: String = String::from("foo");
let os_string: OsString = string.into();
let os_string: OsString = OsString::from("foo");

ベクタから

use std::os::unix::ffi::OsStringExt;
let source: Vec<u8> = vec![0x66, 0x6f, 0x80, 0x6f];
let os_string: OsString = OsString::from_vec(source);


&OsStr と OsString の相互変換

&OsStrOsString

let os_str: &OsStr = OsStr::new("foo");
let os_string: OsString = os_str.to_os_string();

OsString&OsStr

let os_string: OsString = OsString::from("foo");
let os_str: &OsStr = &os_string;
let os_string: OsString = OsString::from("foo");
let os_str: &OsStr = os_string.as_os_str();

strString の関係と同じ。


OsStr と str の相互変換

&OsStr&str への変換は失敗する可能性がある。

let os_str: &OsStr = OsStr::new("foo");
let s: Option<&str> = os_str.to_str();

変換できない文字があった場合、U+FFFD に置き換える。これは必ず成功する。

let os_str: &OsStr = OsStr::new("foo");
let s: Cow<str> = os_str.to_string_lossy();

&str&OsStr は必ず成功する。

let os_str: &OsStr = OsStr::new("foo");


OsString と String の相互変換

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

let os_string: OsString = OsString::from("foo");
let string: Result<String, OsString> = os_string.into_string();

StringOsString への変換は必ず成功する。

let os_string: OsString = string.into();

変換できない文字があった場合、U+FFFD に置き換える場合は、to_string_lossy()Cow<str> として得ることができる。

let os_string: OsString = OsString::from("foo");
let str: Cow<str> = os_string.to_string_lossy();


std::env::Args の例

コマンドライン引数を例に見れば、

let mut args: std::env::Args = std::env::args();
let arg: Option<String> = args.next();

std::env::ArgsOsStringIntoIter として表現されている。

pub struct Args {
    inner: ArgsOs,
}

pub struct ArgsOs {
    inner: sys::args::Args,
}

pub struct sys::args::Args {
    iter: vec::IntoIter<OsString>,
}

args.next() で取得する際に String に変換されるため、OsString を意識することはあまりない。

impl Iterator for Args {
    type Item = String;
    fn next(&mut self) -> Option<String> {
        self.inner.next().map(|s| s.into_string().unwrap())
    }
}


std::fs::DirEntry の例

std::fs::read_dir() によるディレクトリの一覧取得。

let rd: std::fs::ReadDir = std::fs::read_dir("src")?;
for entry in rd {
    let entry: DirEntry = entry?;
    let name: OsString = entry.file_name();
    println!("{:?}", name);
}

DirEntry から PathBuf やファイル名として OsString を取得できる。

impl DirEntry {
    pub fn path(&self) -> PathBuf {
        self.dir.root.join(self.file_name_os_str())
    }

    pub fn file_name(&self) -> OsString {
        self.file_name_os_str().to_os_string()
    }
}