このページの目次

文字列と Path

&str は UTF-8 保証のバイトスライス参照(String はヒープ割り当て版)であり、s[0] でインデックスできません(UTF-8 では 1 文字が 1〜4 バイトのため、O(1) のインデックスは成立しません)。OsStr はプラットフォームネイティブ文字列(Unix ではバイト列、Windows では WTF-8)、PathOsStr のパスラッパーです——クロスプラットフォームなファイル操作では、文字列連結ではなく常に Path/PathBuf を使用するのが正しい做法です。

String と &str: owned vs borrowed

let s: String = String::from("hello");  // ヒープ割り当て、可変、owned
let r: &str = "hello";                  // 静的/借用、不変

String = Vec<u8> + UTF-8 妥当性保証⁠——コンパイラは String の内容が常に有効な UTF-8 であることを保証します。into_bytes() で内部の Vec<u8> を取り出すことができます(UTF-8 保証を放棄)、また from_utf8() でバイトから構築することもできます(Result を返し、無効な UTF-8 の場合は Err を返します)。

s[0] が使えない理由

C では s[0] は byte を返します——ASCII には問題ありませんが、多言語では完全に正しくありません。Python では s[0] は最初の Unicode コードポイントを返しますが、コードポイントは必ずしも「表示可能な文字」とは限りません(é は e + ́ の 2 つのコードポイントになる可能性があります)。

Rust はこの隠れたコストを伴うアクセスをインデックス構文として公開しません:

let s = "こんにちは";                  // 5 文字、UTF-8 では 15 バイト
// let c = s[0];                      // コンパイルエラー: String に対するインデックスは存在しません
let c = s.chars().nth(2).unwrap();    // 'に' — O(2): 最初の 2 文字をスキップする必要があります

UTF-8 は可変長エンコーディングです:ASCII 文字は 1 バイト、西欧/中東の大半の文字は 2 バイト、CJK 文字は 3 バイト、絵文字は 4 バイトです。Rust で s[n] が O(1) になることはあり得ないため、あえて提供していません。プログラマは明示的に .chars().nth(n) と書く必要があり、「これにはスキャンが必要である」ことを認めることになります。

OsStr/OsString: UTF-8 ではないシステム文字列

Unix 上のファイル名は単なる [u8] です——任意のバイト列であり、UTF-8 である保証はありません。Windows 上のファイル名は WTF-8 です(UTF-16 に近い)。OsStr はクロスプラットフォームな抽象化であり、内容に対して UTF-8 保証を行いませんが、安全に渡したり連結したりできます:

use std::ffi::OsStr;
use std::path::{Path, PathBuf};

let p = Path::new("/usr/bin");
let parent = p.parent().unwrap();    // /usr
let file = p.file_name().unwrap();   // bin
let full: PathBuf = p.join("subdir").join("file.txt");  // /usr/bin/subdir/file.txt

PathOsStr のラッパーであり、パス固有のメソッド(parent, extension, components, join)が追加されています。PathBuf は owned 版です。重要な点は、Path は内容を String に変換することを要求しないことです——Path 上で直接操作でき、事前に UTF-8 チェックを行う必要はありません。

参考

  • Rust Book: Chapter 8.2
  • Rust Reference: OsStr, Path

Keywords: String, &str, UTF-8, char, OsStr, Path, PathBuf, indexing, Unicode