Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

所有権とは何か?

所有権 とは、Rust プログラムがどのようにメモリを管理するかを規定する一連のルールです。 すべてのプログラムは、実行中にコンピュータのメモリをどのように使うかを管理しなければなりません。 言語によっては、プログラムの実行中に、もはや使われていない メモリを定期的に探すガベージコレクションを備えています。別の言語では、プログラマーが明示的に メモリを確保し、解放しなければなりません。Rust は第三のアプローチを採用しています。つまり、メモリは コンパイラが検査する一連のルールを持つ所有権システムを通じて管理されます。これらのルールの いずれかに違反すると、プログラムはコンパイルされません。所有権の機能はいずれも、 実行中のプログラムを遅くすることはありません。

所有権は多くのプログラマーにとって新しい概念であるため、慣れるまでには少し時間がかかります。 朗報なのは、Rust と所有権システムのルールに習熟すればするほど、 安全で効率的なコードを自然に書けるようになることです。ぜひ取り組み続けてください!

所有権を理解すると、Rust を独自のものにしている機能を理解するための しっかりした土台が得られます。この章では、非常によく使われるデータ構造である 文字列に焦点を当てたいくつかの例を通して、所有権を学んでいきます。

スタックとヒープ

多くのプログラミング言語では、スタックや ヒープについて頻繁に考える必要はありません。しかし、Rust のようなシステムプログラミング言語では、値が スタック上にあるのかヒープ上にあるのかが、言語の振る舞いや、 なぜ特定の判断をしなければならないのかに影響します。この章の後半では、所有権の一部を スタックとヒープとの関係の中で説明するので、その準備として ここで簡単に説明しておきます。

スタックとヒープはどちらも、実行時にコードが利用できるメモリの一部ですが、 その構造は異なります。スタックは、 値を受け取った順に格納し、取り出すときはその逆順に取り出します。 これは 後入れ先出し(LIFO) と呼ばれます。皿の 積み重ねを考えてみてください。皿を追加するときは山の一番上に置き、 皿が必要なときは一番上から 1 枚取ります。真ん中や下から 皿を追加したり取り出したりしても、うまくいきません! データを追加することは スタックにプッシュすること と呼ばれ、 データを取り除くことは スタックからポップすること と呼ばれます。スタックに格納される すべてのデータは、既知の固定サイズでなければなりません。コンパイル時点でサイズが不明なデータや、 サイズが変わる可能性のあるデータは、代わりにヒープに格納しなければなりません。

ヒープはあまり整理されていません。ヒープにデータを置くときは、 一定量の領域を要求します。メモリアロケータは、ヒープの中から 十分な大きさの空いている場所を見つけ、それを使用中としてマークし、その場所のアドレスである ポインタ を返します。この処理は ヒープへの割り当て と呼ばれ、 単に 割り当て と略されることもあります(スタックに値をプッシュすることは 割り当てとは見なされません)。ヒープへのポインタは既知の固定サイズなので、 そのポインタ自体はスタックに格納できますが、実際のデータが欲しい場合は、 そのポインタをたどらなければなりません。レストランで席に案内される場面を 考えてみてください。店に入ると、グループの人数を伝え、 案内係が全員が座れる空いているテーブルを見つけて、そこへ案内してくれます。 グループの誰かが遅れて来た場合、その人はあなたたちがどこに案内されたかを聞いて、 あなたたちを見つけることができます。

スタックへのプッシュがヒープへの割り当てよりも速いのは、 アロケータが新しいデータを格納する場所を探す必要がまったくなく、その場所が 常にスタックの先頭だからです。対照的に、ヒープに領域を割り当てるには より多くの作業が必要です。というのも、アロケータはまずデータを保持するのに十分な大きさの 領域を見つけ、その後、次の 割り当てに備えるための管理処理を行わなければならないからです。

ヒープ上のデータへのアクセスは、一般にスタック上のデータへのアクセスよりも遅くなります。 そこに到達するにはポインタをたどらなければならないからです。現代の プロセッサは、メモリ上を飛び回ることが少ないほど高速に動作します。 このたとえを続けると、レストランの給仕が複数のテーブルから注文を取る場面を考えてください。 1 つのテーブルの注文をすべて取ってから次のテーブルへ移るのが、 最も効率的です。テーブル A の注文を取り、次にテーブル B の注文を取り、 さらに আবার A、その後また B というように進めるのは、はるかに遅い 処理になります。同じように、プロセッサも、ほかのデータに近い場所にあるデータ (スタック上のように)を扱うほうが、遠く離れたデータ (ヒープ上にあり得るように)を扱うよりも、たいていうまく仕事ができます。

コードが関数を呼び出すと、関数に渡された値 (場合によってはヒープ上のデータへのポインタも含みます)と、その関数の ローカル変数はスタックにプッシュされます。関数の処理が終わると、それらの 値はスタックからポップされます。

コードのどの部分がヒープ上のどのデータを使っているかを追跡すること、 ヒープ上の重複データの量を最小限にすること、そして 領域不足にならないようヒープ上の未使用データを片づけることは、 いずれも所有権が扱う問題です。いったん所有権を理解すれば、 スタックやヒープについて頻繁に考える必要はなくなります。 しかし、所有権の主な目的がヒープデータを管理することだと知っておくと、 所有権がそのように機能する理由を理解する助けになります。

所有権のルール

まず、所有権のルールを見てみましょう。これらを説明する例を 見ていく間、次のルールを念頭に置いてください。

  • Rust の各値には 所有者 がいます。
  • ある時点で所有者になれるのは 1 つだけです。
  • 所有者がスコープを外れると、その値はドロップされます。

変数のスコープ

基本的な Rust 構文はすでに終えたので、これ以降の例では fn main() { のコードをすべて含めません。したがって、もし手元で試しながら読み進めているなら、 次の例を手動で main 関数の中に入れるようにしてください。その結果、 例は少し簡潔になり、定型的なコードではなく実際の詳細に集中できます。

所有権の最初の例として、いくつかの変数のスコープを見ていきます。スコープ とは、 ある項目が有効であるプログラム内の範囲のことです。次の 変数を見てください。

#![allow(unused)]
fn main() {
let s = "hello";
}

変数 s は文字列リテラルを参照しており、その文字列の値は プログラムのテキスト内にハードコードされています。この変数は、宣言された時点から 現在のスコープの終わりまで有効です。リスト 4-1 は、 変数 s がどこで有効になるかをコメントで注釈したプログラムを示しています。

fn main() {
    {                      // s is not valid here, since it's not yet declared
        let s = "hello";   // s is valid from this point forward

        // do stuff with s
    }                      // this scope is now over, and s is no longer valid
}

言い換えると、ここでは 2 つの重要な時点があります。

  • s がスコープ 内に入る と、有効になります。
  • スコープ 外に出る まで、有効なままです。

この時点では、スコープと変数が有効であるタイミングとの関係は、 ほかのプログラミング言語と似ています。ここからさらに理解を積み上げるために、 String 型を導入します。

String

所有権のルールを説明するには、第3章の「データ型」節で扱ったものよりも複雑なデータ型が必要です。これまでに扱った型はサイズが既知で、スタックに格納でき、スコープが終わるとスタックからポップされます。また、コードの別の部分が別のスコープで同じ値を使う必要がある場合でも、新しい独立したインスタンスを作るために、すばやく簡単にコピーできます。しかし、ここではヒープに格納されるデータを見て、Rustがいつそのデータを解放すべきかをどのように知るのかを探りたいので、String型はそのよい例です。

ここでは、所有権に関係する String の部分に集中します。これらの側面は、標準ライブラリが提供するものでも、自分で作成したものでも、ほかの複雑なデータ型にも当てはまります。所有権に関係しない String の側面については、第8章で説明します。

文字列リテラルについてはすでに見てきました。これは、文字列値がプログラム内にハードコードされているものです。文字列リテラルは便利ですが、テキストを使いたいあらゆる状況に適しているわけではありません。理由の1つは、それらがイミュータブルであることです。もう1つは、コードを書く時点ですべての文字列値がわかっているとは限らないことです。たとえば、ユーザー入力を受け取って保存したい場合はどうでしょうか。こうした状況のために、Rustには String 型があります。この型はヒープに確保されたデータを管理するため、コンパイル時には未知の量のテキストを格納できます。次のように、文字列リテラルから from 関数を使って String を作成できます。

#![allow(unused)]
fn main() {
let s = String::from("hello");
}

二重コロン :: 演算子を使うと、この特定の from 関数を string_from のような名前にするのではなく、String 型の名前空間の下に置けます。この構文については、第5章の「メソッド」節でさらに説明し、第7章の「モジュールツリーの要素を参照するためのパス」でモジュールによる名前空間分けについて話すときにも触れます。

この種の文字列は 変更できます:

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

    s.push_str(", world!"); // push_str() appends a literal to a String

    println!("{s}"); // this will print `hello, world!`
}

では、ここでの違いは何でしょうか。なぜ String は変更できるのにリテラルは変更できないのでしょうか。違いは、この2つの型がメモリをどのように扱うかにあります。

メモリと割り当て

文字列リテラルの場合、その内容はコンパイル時にわかっているので、テキストは最終的な実行可能ファイルに直接ハードコードされます。これが、文字列リテラルが高速かつ効率的である理由です。しかし、これらの性質は文字列リテラルがイミュータブルであることによってのみ成り立っています。残念ながら、コンパイル時にはサイズが不明で、プログラム実行中にサイズが変わるかもしれないテキストの断片ごとに、メモリの塊をバイナリに埋め込むことはできません。

String 型では、変更可能で伸長可能なテキスト片をサポートするために、その内容を保持するための、コンパイル時には不明な量のメモリをヒープ上に確保する必要があります。これは次のことを意味します。

  • そのメモリは実行時にメモリアロケータへ要求しなければなりません。
  • String を使い終えたときに、このメモリをアロケータへ返す方法が必要です。

最初の部分は私たちが行います。String::from を呼び出すと、その実装が必要なメモリを要求します。これは、プログラミング言語ではほぼ普遍的なことです。

しかし、2つ目の部分は異なります。ガベージコレクタ (GC) のある言語では、GCがもう使われていないメモリを追跡して片付けてくれるため、私たちはそれを気にする必要がありません。GCのないほとんどの言語では、メモリがもう使われていないことを見極めて、それを明示的に解放するコードを呼び出すのは私たちの責任です。これは、メモリを要求したときと同じです。これを正しく行うことは、歴史的に難しいプログラミング上の問題でした。忘れればメモリを浪費します。早すぎれば無効な変数ができてしまいます。2回行えば、それもバグです。ちょうど1回の allocate に対して、ちょうど1回の free を対応させる必要があります。

Rustは異なる道を取ります。所有している変数がスコープを抜けると、そのメモリは自動的に返されます。文字列リテラルの代わりに String を使った、リスト4-1のスコープの例のバージョンを示します。

fn main() {
    {
        let s = String::from("hello"); // s is valid from this point forward

        // do stuff with s
    }                                  // this scope is now over, and s is no
                                       // longer valid
}

String が必要とするメモリをアロケータに返せる自然なタイミングがあります。それは、s がスコープを抜けるときです。変数がスコープを抜けると、Rustは特別な関数を呼び出してくれます。この関数は drop と呼ばれ、String の作者はここにメモリを返すコードを書けます。Rustは閉じ波括弧で自動的に drop を呼び出します。

注: C++では、オブジェクトのライフタイムの終わりにリソースを解放するこのパターンは、Resource Acquisition Is Initialization (RAII) と呼ばれることがあります。 RAIIパターンを使ったことがあるなら、Rustの drop 関数にはなじみがあるでしょう。

このパターンは、Rustコードの書かれ方に深い影響を与えます。今は単純に見えるかもしれませんが、ヒープに確保したデータを複数の変数で使いたい、より複雑な状況では、コードの振る舞いが予想外に思えることがあります。では、そのような状況をいくつか見ていきましょう。

ムーブによる変数とデータの相互作用

Rustでは、複数の変数が同じデータとさまざまな方法で相互作用できます。リスト4-2は、整数を使った例を示しています。

fn main() {
    let x = 5;
    let y = x;
}

おそらく、これが何をしているかは想像できるでしょう。すなわち、「値 5x に束縛し、その後、x の値のコピーを作って y に束縛する」ということです。これで、xy という2つの変数があり、どちらも 5 に等しくなります。実際、そのとおりのことが起きています。整数はサイズが既知で固定された単純な値であり、この2つの 5 の値はスタックに積まれるからです。

では、String の版を見てみましょう。

fn main() {
    let s1 = String::from("hello");
    let s2 = s1;
}

これはとてもよく似ているので、動作も同じだと思うかもしれません。つまり、2行目で s1 の値のコピーが作られ、それが s2 に束縛される、ということです。しかし、実際には少し違います。

String の内部で何が起きているのかを図4-1で見てみましょう。String は左側に示されている3つの部分から成ります。すなわち、文字列の内容を保持するメモリへのポインタ、長さ、容量です。このデータのまとまりはスタックに格納されます。右側は、その内容を保持するヒープ上のメモリです。

2つの表: 1つ目の表には、スタック上の s1 の表現が含まれており、
長さ (5)、容量 (5)、および2つ目の表の最初の値へのポインタで構成されて
います。2つ目の表には、ヒープ上の文字列データの表現が、バイトごとに含ま
れています。 図4-1: s1 に束縛された値 "hello" を保持する String のメモリ内表現

長さは、String の内容が現在使用しているメモリ量をバイト単位で表したものです。容量は、String がアロケータから受け取ったメモリの総量をバイト単位で表したものです。長さと容量の違いは重要ですが、この文脈では重要ではないので、今のところ容量は無視してかまいません。

s1s2 に代入すると、String のデータがコピーされます。つまり、スタック上にあるポインタ、長さ、容量をコピーします。ポインタが参照しているヒープ上のデータはコピーしません。言い換えると、メモリ内のデータ表現は図4-2のようになります。

3つの表: それぞれスタック上のそれらの文字列を表す表 s1 と s2 があり、
どちらもヒープ上の同じ文字列データを指している。

図4-2: s1 のポインタ、長さ、容量のコピーを持つ変数 s2 のメモリ内表現

この表現は図4-3のようには なりません 。図4-3は、Rust が代わりにヒープ上のデータもコピーした場合にメモリがどう見えるかを示したものです。もし Rust がそうしていたら、ヒープ上のデータが大きい場合、s2 = s1 という操作は実行時性能の面で非常に高コストになり得ます。

4つの表: 2つの表は s1 と s2 のスタックデータを表し、
それぞれがヒープ上の文字列データの自分自身のコピーを指している。

図4-3: Rust がヒープ上のデータもコピーした場合に s2 = s1 が 行う可能性のある別の動作

先ほど、変数がスコープを抜けると、Rust は自動的に drop 関数を呼び出し、その変数のヒープメモリを片付けると述べました。しかし、図4-2では両方のデータポインタが同じ場所を指しています。これは問題です。s2s1 がスコープを抜けると、両方が同じメモリを解放しようとするからです。これは 二重解放 エラーとして知られており、先ほど触れたメモリ安全性バグの1つです。メモリを2回解放するとメモリ破壊につながる可能性があり、それがセキュリティ上の脆弱性につながるおそれがあります。

メモリ安全性を確保するために、let s2 = s1; という行の後、Rust は s1 をもはや有効ではないとみなします。したがって、s1 がスコープを抜けるときに Rust は何も解放する必要がありません。s2 が作成された後で s1 を使おうとするとどうなるか見てみましょう。うまく動きません。

fn main() {
    let s1 = String::from("hello");
    let s2 = s1;

    println!("{s1}, world!");
}

無効化された参照を使えないように Rust が防ぐため、次のようなエラーが出ます。

$ cargo run
   Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0382]: borrow of moved value: `s1`
 --> src/main.rs:5:16
  |
2 |     let s1 = String::from("hello");
  |         -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
3 |     let s2 = s1;
  |              -- value moved here
4 |
5 |     println!("{s1}, world!");
  |                ^^ value borrowed here after move
  |
  = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider cloning the value if the performance cost is acceptable
  |
3 |     let s2 = s1.clone();
  |                ++++++++

For more information about this error, try `rustc --explain E0382`.
error: could not compile `ownership` (bin "ownership") due to 1 previous error

他の言語を扱っているときに shallow copydeep copy という用語を聞いたことがあるなら、データをコピーせずにポインタ、長さ、容量をコピーするという概念は、シャローコピーを行っているように聞こえるでしょう。しかし、Rust は最初の変数も無効化するため、これはシャローコピーと呼ばれるのではなく、ムーブ として知られています。この例では、s1s2ムーブされた と言います。つまり、実際に起きていることは図4-4に示されているとおりです。

3つの表: それぞれスタック上のそれらの文字列を表す表 s1 と s2 があり、
どちらもヒープ上の同じ文字列データを指している。
表 s1 は、s1 がもはや有効ではないためグレーアウトされている。ヒープデータに
アクセスするために使えるのは s2 だけである。

図4-4: s1 が無効化された後のメモリ内表現

これで問題は解決です! 有効なのは s2 だけなので、これがスコープを抜けるときにそれだけがメモリを解放し、これで完了です。

さらに、ここから暗黙に示される設計上の選択があります。Rust はあなたのデータの「ディープ」コピーを自動的に作成することは決してありません。したがって、どのような 自動的な コピーも、実行時性能の面では低コストであると考えられます。

スコープと代入

これとは逆のことが、スコープ、所有権、そして drop 関数によるメモリ解放の関係についても当てはまります。既存の変数にまったく新しい値を代入すると、Rust は drop を呼び出し、元の値のメモリを即座に解放します。たとえば、次のコードを考えてみましょう。

fn main() {
    let mut s = String::from("hello");
    s = String::from("ahoy");

    println!("{s}, world!");
}

最初に、値 "hello" を持つ String に束縛された変数 s を宣言します。次に、すぐに値 "ahoy" を持つ新しい String を作成し、それを s に代入します。この時点では、ヒープ上の元の値を参照しているものは何もありません。図4-5は、この時点のスタックとヒープのデータを示しています。

1つの表がスタック上の文字列値を表し、
ヒープ上の2つ目の文字列データ (ahoy) を指している。元の文字列データ
(hello) は、もはやアクセスできないためグレーアウトされている。

図4-5: 初期値が完全に置き換えられた後のメモリ内表現

したがって、元の文字列は即座にスコープを抜けます。Rust はそれに対して drop 関数を実行し、そのメモリはただちに解放されます。最後に値を表示すると、それは "ahoy, world!" になります。

Clone と相互作用する変数とデータ

String のヒープデータを、スタックデータだけでなく深くコピーしたい 場合はclone という一般的なメソッドを使えます。メソッド構文については第5章で説明しますが、メソッドは多くのプログラミング言語に共通する機能なので、おそらく以前に見たことがあるでしょう。

以下は、clone メソッドが動作している例です。

fn main() {
    let s1 = String::from("hello");
    let s2 = s1.clone();

    println!("s1 = {s1}, s2 = {s2}");
}

これはまったく問題なく動作し、ヒープデータが 実際に コピーされる図4-3の挙動を明示的に生み出します。

clone の呼び出しを見ると、何らかの任意のコードが実行され、そのコードは高コストである可能性があるとわかります。これは、何か異なることが起きていることを視覚的に示す指標です。

スタックのみのデータ: Copy

まだ触れていない別のひとひねりがあります。整数を使ったこのコード――その一部はリスト4-2で示しました――は動作し、有効です。

fn main() {
    let x = 5;
    let y = x;

    println!("x = {x}, y = {y}");
}

しかし、このコードは今学んだことと矛盾しているように見えます。clone の呼び出しはありませんが、x は依然として有効で、y にムーブされてもいません。 その理由は、コンパイル時にサイズが既知である整数のような型は、 実際の値全体がスタックに格納されるため、それらのコピーをすばやく 作成できるからです。つまり、変数 y を作成したあとで x が有効で なくなるのを防ぎたい理由はありません。言い換えると、ここではディープ コピーとシャローコピーの違いはないので、clone を呼び出しても通常の シャローコピーと何も変わらず、それを省略できます。

Rust には Copy トレイトと呼ばれる特別な注釈があり、整数のように スタックに格納される型にこれを付けることができます(トレイトについては 第10章 で詳しく説明します)。ある型が Copy トレイトを実装している場合、その型を使う変数はムーブされず、単純に コピーされるため、別の変数への代入後も引き続き有効です。

型またはその一部が Drop トレイトを実装している場合、Rust はその型に Copy を付けることを許しません。値がスコープを外れるときに何らかの 特別な処理が必要な型に Copy 注釈を追加すると、コンパイル時エラーが 発生します。型に Copy 注釈を追加してこのトレイトを実装する方法については、 付録 C の 「導出可能なトレイト」 を 参照してください。

では、どのような型が Copy トレイトを実装しているのでしょうか。確実を 期すには対象の型のドキュメントを確認できますが、一般的な規則としては、 単純なスカラー値の集まりは Copy を実装でき、メモリ割り当てを必要とする ものや何らかのリソースであるものは Copy を実装できません。以下は Copy を実装している型の一部です。

  • u32 など、すべての整数型。
  • truefalse を持つ論理値型 bool
  • f64 など、すべての浮動小数点数型。
  • 文字型 char
  • タプル。ただし、含まれる要素がすべて Copy を実装している場合に限ります。 たとえば、(i32, i32)Copy を実装しますが、(i32, String) は 実装しません。

所有権と関数

値を関数に渡す仕組みは、値を変数に代入するときの仕組みと似ています。 変数を関数に渡すと、代入の場合と同様にムーブまたはコピーが行われます。 リスト 4-3 には、変数がスコープに入る場所と外れる場所を示す注釈付きの 例があります。

fn main() {
    let s = String::from("hello");  // s comes into scope

    takes_ownership(s);             // s's value moves into the function...
                                    // ... and so is no longer valid here

    let x = 5;                      // x comes into scope

    makes_copy(x);                  // Because i32 implements the Copy trait,
                                    // x does NOT move into the function,
                                    // so it's okay to use x afterward.

} // Here, x goes out of scope, then s. However, because s's value was moved,
  // nothing special happens.

fn takes_ownership(some_string: String) { // some_string comes into scope
    println!("{some_string}");
} // Here, some_string goes out of scope and `drop` is called. The backing
  // memory is freed.

fn makes_copy(some_integer: i32) { // some_integer comes into scope
    println!("{some_integer}");
} // Here, some_integer goes out of scope. Nothing special happens.

takes_ownership の呼び出し後に s を使おうとすると、Rust は コンパイル時エラーを発生させます。これらの静的検査は、私たちをミスから 守ってくれます。sx を使うコードを main に追加して、どこで 使えるのか、また所有権のルールによってどこで使えなくなるのかを確認して みてください。

戻り値とスコープ

値を返すことでも所有権は移動します。リスト 4-4 は、何らかの値を返す 関数の例を示しており、リスト 4-3 と同様の注釈が付いています。

fn main() {
    let s1 = gives_ownership();        // gives_ownership moves its return
                                       // value into s1

    let s2 = String::from("hello");    // s2 comes into scope

    let s3 = takes_and_gives_back(s2); // s2 is moved into
                                       // takes_and_gives_back, which also
                                       // moves its return value into s3
} // Here, s3 goes out of scope and is dropped. s2 was moved, so nothing
  // happens. s1 goes out of scope and is dropped.

fn gives_ownership() -> String {       // gives_ownership will move its
                                       // return value into the function
                                       // that calls it

    let some_string = String::from("yours"); // some_string comes into scope

    some_string                        // some_string is returned and
                                       // moves out to the calling
                                       // function
}

// This function takes a String and returns a String.
fn takes_and_gives_back(a_string: String) -> String {
    // a_string comes into
    // scope

    a_string  // a_string is returned and moves out to the calling function
}

変数の所有権は、毎回同じパターンに従います。ある値を別の変数に代入すると、 その値はムーブされます。ヒープ上のデータを含む変数がスコープを外れると、 そのデータの所有権が別の変数にムーブされていない限り、その値は drop に よってクリーンアップされます。

これは機能しますが、すべての関数で所有権を受け取っては返すというのは、 少し面倒です。関数に値を使わせたいだけで、所有権は奪わせたくない場合は どうでしょうか。さらに、渡したものをもう一度使いたいなら返してもらう必要が あるうえに、関数本体の結果として返したいデータも別にあるかもしれないので、 かなり煩わしいです。

Rust では、リスト 4-5 に示すように、タプルを使って複数の値を返すことが できます。

fn main() {
    let s1 = String::from("hello");

    let (s2, len) = calculate_length(s1);

    println!("The length of '{s2}' is {len}.");
}

fn calculate_length(s: String) -> (String, usize) {
    let length = s.len(); // len() returns the length of a String

    (s, length)
}

しかし、これは本来よくあるはずの概念に対して、儀式的すぎて手間がかかり すぎます。幸いなことに、Rust には所有権を移動せずに値を使うための機能が あります。それが参照です。