Rustエラー解決:Stringをusizeでインデックス指定できない理由

beginner🦀 Rust2026-07-01| Rust (全バージョン), Cargo, すべてのOS (Linux, macOS, Windows)

Error Message

error[E0277]: the type `String` cannot be indexed by `usize`
#rust#文字列#インデックス指定##utf8

問題:なぜRustでは s[0] が失敗するのか

Python、JavaScript、C++などの言語を使っていると、文字を取得するためにまず my_string[0] と書きたくなるでしょう。しかしRustでは、この操作は即座にコンパイルエラーを引き起こします。言語仕様として、これを許可していないのです。

error[E0277]: the type `String` cannot be indexed by `usize`
  --> src/main.rs:3:13
   |
 3 |     let c = s[0];
   |             ^^^^ `String` cannot be indexed by `usize`
   |
   = help: the trait `Index<usize>` is not implemented for `String`

Rustが文字列を異なる方法で扱うのは、文字列が Vec<u8> をラップしたUTF-8エンコード形式だからです。UTF-8では、1つの文字は1バイトから4バイトの間のサイズになります。例えば、英字の 'R' は1バイトですが、カニの絵文字 (🦀) は4バイトです。もしRustが s[i] を許可してしまうと、i番目の「バイト」が欲しいのか、それともi番目の「文字」が欲しいのかが不明確になります。バイトのインデックス指定は高速(O(1))ですが、文字のインデックス指定は文字列を走査する必要があるため低速(O(n))です。Rustは、こうした隠れたパフォーマンスコストを避けるために、明示的な記述を強制します。

デバッグプロセス

単純なユーザー入力をパースしようとしている時に、この問題に遭遇するかもしれません。失敗するコード例を見てみましょう:

fn main() {
    let name = String::from("Rust");
    let first_letter = name[0]; // この行でビルドが失敗します
    println!("First letter is: {}", first_letter);
}

コンパイラは、String 型が usize による Index トレイトを実装していないことを指摘しています。解決するには、速度を優先するかUnicodeの安全性を優先するかに基づいて、特定の戦略を選択する必要があります。

解決策1:.chars().nth() を使用する(安全な方法)

特定の文字が必要で、Unicodeを正しく扱いたい場合は、chars() イテレータを使用します。これは、ほとんどのアプリケーションにおいて最も信頼できる方法です。

fn main() {
    let name = String::from("Rust");
    
    // .chars() はUnicode文字をイテレートします
    // .nth(0) は安全に最初の文字の取得を試みます
    if let Some(first_char) = name.chars().nth(0) {
        println!("The first character is: {}", first_char);
    }
}

メリット: "Ferris 🦀" のようなマルチバイト文字を壊さずに扱えます。 デメリット: 実行時間がO(n)になります。100番目の文字を見つけるには、Rustはその前にあるすべてのバイトを確認しなければなりません。

解決策2:文字列スライス(高速だが危険)

文字が確実に1バイト(標準的なASCIIなど)であると確信している場合や、正確なバイト境界がわかっている場合は、スライスを使用できます。これは char ではなく &str を返します。

fn main() {
    let name = String::from("Rust");
    let first_char_slice = &name[0..1]; 
    println!("First char: {}", first_char_slice);
}

警告: マルチバイト文字の途中でスライスを行うと、実行時に panic を引き起こします。プログラムは即座にクラッシュします。

let crab = String::from("🦀");
let fail = &crab[0..1]; // パニックが発生します! カニの絵文字は4バイト長であり、1は境界ではありません。

解決策3:生のバイトにアクセスする

文字を気にせず、生の u8 値だけが必要な場合があります。その場合は as_bytes() を使用します。

fn main() {
    let name = String::from("Rust");
    let first_byte = name.as_bytes()[0];
    println!("First byte value: {}", first_byte); // 'R' のASCII値である 82 が出力されます
}

書記素クラスタ(Grapheme Clusters)の扱い

見た目上の1文字が複数のUnicode値で構成されている場合があります。肌の色を指定した絵文字や、アクセント記号付きの文字(y̆ など)がその例です。これらに対しては .chars() だけでは不十分です。「人間が認識する」文字を扱うには、unicode-segmentation クレートが必要になります。

use unicode_segmentation::UnicodeSegmentation;

fn main() {
    let complex_emoji = "y̆"; // これは 'y' と結合用の記号(ブレーヴェ)の組み合わせです
    let first_grapheme = complex_emoji.graphemes(true).next().unwrap();
    println!("Real first char: {}", first_grapheme);
}

検証:修正を確認する方法

- `cargo check` を実行し、E0277エラーが解消されていることを確認します。
- `cargo run` を実行して出力を確認します。
- `.nth()` を選択した場合は、空の文字列でテストして `Option` の処理が機能することを確認します。
- スライスを選択した場合は、`é` のような非ASCII文字を渡してみて、境界によってパニックが発生しないことを確認します。

学んだこと

- **文字列は配列ではない:** Rustは利便性よりもデータの整合性を優先します。明示的に指示しない限り、UTF-8文字列を単純なバイトのリストとして扱うことはできません。
- **計算量に注意:** 500番目の文字へのアクセスはO(n)の操作です。頻繁にランダムアクセスが必要な場合は、代わりにデータを `Vec<char>` に格納することを検討してください。
- **速度よりも安全性:** デフォルトでは `.chars().nth()` を使用しましょう。スライスやバイトアクセスへの変更は、パフォーマンスのボトルネックが測定され、かつバイト境界が保証できる場合にのみ行ってください。

Related Error Notes