my.code(); Logomy.code();
Rust-4.借用とスライス

my.code(); Logomy.code();

  • C++
    • 0.C++の世界へようこそ
    • 1.型システムと制御構造
    • 2.データ集合とモダンな操作
    • 3.ポインタとメモリ管理
    • 4.関数と参照渡し
    • 5.プロジェクトの分割とビルド
    • 6.クラスの基礎
    • 7.クラスを使いこなす
    • 8.継承とポリモーフィズム
    • 9.テンプレート
    • 10.STL ①:コンテナ
    • 11.STL ②:アルゴリズムとラムダ式
    • 12.RAIIとスマートポインタ
  • JavaScript
    • 0.JavaScriptへようこそ
    • 1.基本構文とデータ型
    • 2.制御構文
    • 3.関数とクロージャ
    • 4.'this'の正体
    • 5.オブジェクトとプロトタイプ
    • 6.クラス構文
    • 7.配列とイテレーション
    • 8.非同期処理①: Promise
    • 9.非同期処理②: Async/Await
  • Python
    • 0.環境構築と基本思想
    • 1.基本構文とデータ型
    • 2.リスト、タプル、辞書、セット
    • 3.制御構文と関数
    • 4.モジュールとパッケージ
    • 5.オブジェクト指向プログラミング
    • 6.ファイルの入出力とコンテキストマネージャ
    • 7.例外処理
    • 8.ジェネレータとデコレータ
  • Ruby
    • 0.rubyの世界へようこそ
    • 1.基本構文とデータ型
    • 2.制御構造とメソッド定義
    • 3.すべてがオブジェクト
    • 4.コレクション (Array, Hash, Range)
    • 5.ブロックとイテレータ
    • 6.クラスとオブジェクト
    • 7.モジュールとMix-in
    • 8.Proc, Lambda, クロージャ
    • 9.標準ライブラリの活用
    • 10.テスト文化入門
    • 11.メタプログラミング入門
  • Rust
    • 0.Rustの世界へようこそ
    • 1.基本構文と「不変性」
    • 2.関数と制御フロー
    • 3.所有権
    • 4.借用とスライス
    • 5.構造体とメソッド構文
    • 6.列挙型とパターンマッチ
    • 7.モジュールシステムとパッケージ管理
    • 8.コレクションと文字列
    • 9.エラーハンドリング
    • 10.ジェネリクスとトレイト
    • 11.ライフタイム
  • TypeScript
    • 0.TypeScriptへようこそ
    • 1.基本的な型と型推論
    • 2.オブジェクト、インターフェース、型エイリアス
    • 3.関数の型定義
    • 4.型を組み合わせる
    • 5.ジェネリクス
    • 6.クラスとアクセス修飾子
    • 7.非同期処理とユーティリティ型
my.code(); Logomy.code();

環境構築不要、その場で実践。

ut-code / my-code

Copyright © 2026 ut.code();

my.code(); について
コード実行環境についてAI質問機能についてライセンスお問い合わせ
ut.code(); について
公式ウェブサイト公式 𝕏 アカウント

第4章: 借用(Borrowing)とスライス

前章では「所有権(Ownership)」というRust独自のメモリ管理システムについて学びました。「所有権は一度に1つの変数しか持てない」というルールは強力ですが、関数に値を渡すたびに所有権が移動(ムーブ)してしまうと、毎回値を返り値として受け取らなければならず不便です。

そこで登場するのが「参照(Reference)」と「借用(Borrowing)」です。これを使うことで、所有権を渡さずに値にアクセスすることが可能になります。

参照と借用(& と &mut)

参照(Reference)とは、所有権を持たずにデータへアクセスするためのポインタのようなものです。他の言語のポインタと似ていますが、Rustの参照は「常に有効なデータを指していること」が保証されています。

関数の引数として参照を渡すことを、Rustでは「借用(Borrowing)」と呼びます。誰かから本を借りても、それを捨てたり(メモリ解放)、転売したり(所有権の譲渡)できないのと同じです。

不変参照(Immutable Reference)

デフォルトでは、参照は不変です。借りた値を読むことはできますが、変更することはできません。参照を作成するには & を使います。

ファイルを編集:calculate_length.rs
fn main() {
    let s1 = String::from("hello");

    // &s1 で s1 への参照を渡す(所有権は移動しない)
    let len = calculate_length(&s1);

    // 所有権は移動していないので、ここで s1 をまだ使える!
    println!("The length of '{}' is {}.", s1, len);
}

// 引数の型が &String になっていることに注目
fn calculate_length(s: &String) -> usize {
    s.len()
} // ここで s がスコープを抜けるが、所有権を持っていないのでメモリは解放されない
ブラウザ上で動作するrustの実行環境です。
左上の実行ボタンを押して、このページ内のcalculate_length.rsに書かれている内容を実行します。
The length of 'hello' is 5.

可変参照(Mutable Reference)

借りた値を変更したい場合は、可変参照を使用します。これには &mut を使い、元の変数も mut である必要があります。

ファイルを編集:mutable_borrow.rs
fn main() {
    let mut s = String::from("hello");

    change(&mut s); // 可変参照を渡す

    println!("Result: {}", s);
}

fn change(some_string: &mut String) {
    some_string.push_str(", world");
}
ブラウザ上で動作するrustの実行環境です。
左上の実行ボタンを押して、このページ内のmutable_borrow.rsに書かれている内容を実行します。
Result: hello, world

借用のルール

Rustには、メモリ安全性とデータ競合(Data Race)を防ぐための非常に重要なルールがあります。これを「借用のルール」と呼びます。

【借用の鉄則】 ある特定のスコープにおいて、以下のどちらか一方しか満たすことはできません:

  1. 1つの可変参照(&mut T)を持つ。
  2. 任意の数の不変参照(&T)を持つ。

なぜ可変参照は1つだけなのか?

もし「同じデータに対する可変参照」が同時に2つ存在したらどうなるでしょうか? Aさんがデータを書き換えている最中に、Bさんも書き換えようとすると、データが破損する可能性があります。Rustはこの可能性をコンパイル時に排除します。

不変参照と可変参照の共存禁止

同様に、「誰かがデータを読んでいる最中(不変参照)に、誰かがデータを書き換える(可変参照)」ことも禁止されています。読んでいる最中にデータが変わると困るからです。

以下のコードはコンパイルエラーになります(概念を示すための例です)。

let mut s = String::from("hello");

let r1 = &s; // 問題なし
let r2 = &s; // 問題なし
let r3 = &mut s; // 大問題!不変参照が存在している間に可変参照は作れない

println!("{}, {}, and {}", r1, r2, r3);

しかし、スコープを利用すれば、この制限をクリアできます。直前の参照が使用されなくなった(スコープを抜けた)後であれば、新しい参照を作ることができます。

ファイルを編集:borrow_scope.rs
fn main() {
    let mut s = String::from("hello");

    {
        let r1 = &mut s;
        r1.push_str(" world");
        // r1 はここでスコープを抜ける
    } 

    // r1 はもういないので、新しい不変参照を作れる
    let r2 = &s;
    println!("Final string: {}", r2);
}
ブラウザ上で動作するrustの実行環境です。
左上の実行ボタンを押して、このページ内のborrow_scope.rsに書かれている内容を実行します。
Final string: hello world

ダングリングポインタの防止

C++などの言語では、メモリが解放された後の場所を指し続けるポインタ(ダングリングポインタ)を作ってしまうことがあり、これが重大なバグの原因になります。 Rustでは、コンパイラがこれを絶対に許可しません。

以下のコードは、「関数内で作成した変数の参照」を返そうとしていますが、これはエラーになります。

// コンパイルエラーになる例
fn dangle() -> &String {
    let s = String::from("hello");
    &s // sの参照を返す
} // ここでsはスコープを抜け、メモリが解放される。参照先が消滅する!

Rustコンパイラは「データのライフタイム(生存期間)が、その参照よりも短い」と判断し、エラーを出してくれます。この場合は、参照ではなく値を返して所有権を移動させるのが正解です。

スライス型(Slice)

参照の特殊な形として「スライス(Slice)」があります。スライスは、コレクション(配列や文字列など)の一部分への参照です。所有権を持たない点は通常の参照と同じです。

文字列スライス(&str)

特に重要なのが文字列スライスです。String の一部を切り出して参照します。 書き方は [開始インデックス..終了インデックス] です。

ファイルを編集:string_slices.rs
fn main() {
    let s = String::from("Hello Rust World");

    let hello = &s[0..5]; // 0番目から4番目まで(5は含まない)
    let rust = &s[6..10]; // 6番目から9番目まで

    println!("Slice 1: {}", hello);
    println!("Slice 2: {}", rust);

    // 省略記法
    let len = s.len();
    let start = &s[0..5];
    let start_short = &s[..5]; // 0は省略可能

    let end = &s[6..len];
    let end_short = &s[6..];   // 末尾も省略可能

    let all = &s[..];          // 全体
    
    println!("Full: {}", all);
}
ブラウザ上で動作するrustの実行環境です。
左上の実行ボタンを押して、このページ内のstring_slices.rsに書かれている内容を実行します。
Slice 1: Hello
Slice 2: Rust
Full: Hello Rust World

文字列リテラルはスライスである

これまで何気なく使っていた文字列リテラルですが、実はこれこそがスライスです。

let s = "Hello, world!";

ここで s の型は &str です。これはバイナリの静的領域に格納されている文字列データへのスライス(参照)なのです。

引数としての &str

関数で文字列を受け取る際、&String よりも &str を使う方が柔軟性が高まります。なぜなら、&str を引数にすれば、String も &str(リテラルなど)も両方受け取れるからです。

// この定義の方が汎用的
fn first_word(s: &str) -> &str {
    // 実装...
    &s[..] // 仮の実装
}

その他のスライス

スライスは文字列だけでなく、配列に対しても使えます。

ファイルを編集:array_slice.rs
fn main() {
    let a = [10, 20, 30, 40, 50];

    // 配列の一部を借用する
    let slice = &a[1..3]; // [20, 30]

    // assert_eq! は値が等しいか確認するマクロ
    assert_eq!(slice, &[20, 30]);
    
    println!("Slice elements: {:?}", slice);
}
ブラウザ上で動作するrustの実行環境です。
左上の実行ボタンを押して、このページ内のarray_slice.rsに書かれている内容を実行します。
Slice elements: [20, 30]

この章のまとめ

  • 参照(&): 所有権を移動させずに値にアクセスする仕組み。
  • 借用: 関数の引数として参照を渡すこと。
  • 借用のルール:
    1. 不変参照(&T)は同時にいくつでも作れる。
    2. 可変参照(&mut T)は同時に1つしか作れない。
    3. 不変参照と可変参照は同時に存在できない。
  • スライス: コレクションの一部分を参照するビュー。&str は文字列の一部への不変参照。

Rustのコンパイラ(ボローチェッカー)は厳格ですが、それはバグのない安全なコードを書くための強力なパートナーです。慣れてくれば、コンパイルが通った時点でロジックの正しさに自信が持てるようになります。

練習問題 1: 参照渡しへの書き換え

以下のコードは動作しますが、calculate_area 関数が所有権を奪ってしまうため、再度 rect1 を使おうとするとエラーになります。 calculate_area が Rectangle の不変参照を受け取るように修正し、main 関数で rect1 を再利用できるようにしてください。

ファイルを編集:practice5_1.rs
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let rect1 = Rectangle { width: 30, height: 50 };

    let area = calculate_area(rect1); // ここを修正
    
    // 現在のコードだと、ここで rect1 を使うとエラーになる
    // println!("rect1 is {:?}", rect1); 
    
    println!("The area is {}", area);
}

fn calculate_area(rect: Rectangle) -> u32 { // ここを修正
    rect.width * rect.height
}
ブラウザ上で動作するrustの実行環境です。
左上の実行ボタンを押して、このページ内のpractice5_1.rsに書かれている内容を実行します。

練習問題 2: スライスの活用

Eメールアドレスを表す文字列を受け取り、「@」より前のユーザー名部分だけを文字列スライス(&str)として返す関数 extract_username を作成してください。もし「@」が含まれていない場合は、文字列全体を返してください。

ヒント: 文字列をバイト配列として扱い、ループで @ を探します。

ファイルを編集:practice5_2.rs
fn main() {
    let email = String::from("user@example.com");
    let username = extract_username(&email);
    println!("Username: {}", username); // "user" と出力されるべき

    let simple = String::from("admin");
    let simple_name = extract_username(&simple);
    println!("Username: {}", simple_name); // "admin" と出力されるべき
}

fn extract_username(s: &str) -> &str {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        // ここにコードを書く

    }

    // 「@」が含まれていない場合は、文字列全体を返す

}
ブラウザ上で動作するrustの実行環境です。
左上の実行ボタンを押して、このページ内のpractice5_2.rsに書かれている内容を実行します。
Username: user
Username: admin
前のページ« 所有権
次のページ構造体とメソッド構文 »