Programmingバックエンド開発者

Rustのスライスのメカニズムはどのように機能し、どのようなタイプのスライスが存在し、安全性はどのように保証され、スライスの範囲を超えようとした場合はどうなりますか?

Hintsage AIアシスタントで面接を突破

回答

Rustのスライスは、メモリ内に順次配置されたコレクションの一部を動的に表現するものです。スライスの典型的な例は&[T]または&mut [T]です。スライスはデータを所有せず、外部バッファを参照します。すべてのスライスは、参照と共に保存される長さを持っています。

主なタイプ:

  • &[T] — 不変スライス
  • &mut [T] — 可変スライス
  • 文字列の場合: &str(実際には、常に正しいUTF-8のバイトスライス)

スライスの安全性は、コンパイラとランタイムのレベルで保証されています。スライスの範囲外の要素にアクセスしようとすると、実行時にパン(パニック)が発生し、C/C++のようにメモリの不整合エラーは発生しません。

例:

let v = vec![1, 2, 3, 4, 5]; let s: &[i32] = &v[1..4]; // スライス、要素2、3、4を参照 println!("{:?}", s); // [2, 3, 4] // s[3]; // panic! (範囲外アクセス)

限界のある質問

スライスは空であることができますか?空のスライスとNoneの違いは何ですか?

回答: はい、スライスは空であり得ます(&[])が、これは長さ0のデータの一部を指すものであり、Noneの類似物ではありません。空のスライスは安全に使用でき、Option<&[T]>は「スライスが存在するかどうか」を区別するために使われます。

例:

let s: &[i32] = &[]; assert!(s.is_empty()); // スライスが全くない可能性がある場合はOption<&[i32]>を使用します。

知識不足による実際のエラーの例


物語

大規模なログサービスで、あるプログラマーはユーザーデータから動的に計算された範囲でスライスを取得しました。スライスが間違って計算された場合(例えば、start > endまたはend > len)、コードはテストで機能しましたが、本番環境では「panic」を引き起こし、負荷のピーク時にプロセスがクラッシュしました。


物語

内部のコンカレントハッシュライブラリでは、&mut [T]を使用し、複数のスレッドが同じ配列の異なるスライスを同時に取得していました。あるスレッドがスライスを変更し、別のスレッドが同じメモリを参照して他のスライスを取得しました。プログラムはコンパイルされましたが、不適切な分割により、スライスが交差した場合に不正な動作(UB)が発生する可能性がありました(unsafeコードを使用)。


物語

ネットワークパケットのシステムパーサーで、安全でない方法(生ポインタとfrom_raw_parts)でスライスが作成されました。開発者は入力パケットの長さの正当性を確認するのを忘れました。その結果、範囲外の読み取りを試みることでアプリケーションがクラッシュし、潜在的なOOBアクセスの脆弱性が発生しました。これは安全なスライスを正しく適用することで回避できたかもしれません。