BackEnd

Rust入門してみた その5 ライフタイム

投稿日:

はじめに

Rustには、借用した値の参照の寿命を意味する「ライフタイム」という概念があり、この仕組みによって破棄された所有に対する参照をしてしまうことがないように、コンパイル時にチェックします。
多くの場合では、ライフタイムをさほど意識する必要はなさそうですが、複数のライフタイムを返す関数を作成する場合など、ライフタイムを意識せざるを得ない場合もあるため、ライフタイムの概念について抑えておきたいと思います。

ライフタイムと借用チェッカー

Rustのコンパイラには借用チェッカーがあり、スコープを抜けて破棄された参照にアクセスしようとしていないか、コンパイル時にチェックします。
例えば、以下のコードはコンパイルエラーになります。

Rustでは、所有はスコープ内でのみ有効であり、スコープを抜けると破棄されます。このコードでは、内側のスコープで宣言されたxへの参照を、外側のスコープのrに入れようとします。しかし、xは内側のスコープを抜けると破棄されてしまうため、rはその後使用できなくなってしまいます。
このような状況を「ダングリング参照」と呼びます。

この、借用できる有効範囲こそがライフタイムのことです。上記のコードでは、’a と ‘b のライフタイムがあるとして、改めてソースコードを見てみます。
そうすると、 ‘a より ‘b のほうがライフタイムが短いのに、 ‘a は ‘b への参照を持ってしまっています。

このコードを修正すると、次のようになります。

このように、Rustで借用をする場合には、常にライフタイムを意識して、破棄された所有への参照が残らないようにする必要があります。

コンパイラにライフタイムを教える

借用チェッカーはある程度のライフタイムはコードから推論してくれますが、中には分からないものもあります。そうした場合には、ライフタイムをコード上で教える必要があります。
例えば、次のコードを見てください。

sliece_by_first_sentence() は第1引数textのなかから最初の1文を探して、見つかればそこまでの文字列スライスを返します。(文字列スライスは、Stringの一部への参照です。)
見つからなければ、第2引数defaultをそのまま返します。
(ちなみに引数のライフタイムを「入力ライフタイム」、戻り値のライフタイムは「出力ライフタイム」と称されます。)

ここで、main関数を見てみましょう。text, first_sentenceは外側のスコープにいますが、default_valは内側のスコープにします。そして、default_valは内側のスコープを抜けたタイミングで破棄されます。
しかし、slice_by_first_sentenceの戻り値として、第2引数に渡したdefault_valの参照がそのまま返ってくる可能性があり、その場合はやはりダングリング参照が起きてしまいます。

ですが、このようなケースは借用チェッカーでは検知できず、別のコンパイルエラーとなります。
なぜなら、 slice_by_first_sentnce の実際の戻り値が、textなのかdefault_valなのか、コンパイラには分からないからです。
こうした場合には、次のようにライフタイム注釈をします。

ライフタイム注釈の書き方はちょっと特殊で 'a のように書きます。
上記のコードは、textとdefaultの両方ともがとある 'a というライフタイムであり、戻り値もまた 'a ライフタイムであることをコンパイラに教えています。

ちなみに <'a> というようにジェネリクスに準じたシンタックスであることから想像が付くかもしれませんが、このライフタイム注釈はジェネリックなものです。
そのためtextやdefault_valの実際のライフタイムはこの関数の関心ごとではなく、渡されてきたそれぞれの参照のライフタイムのうち、どのライフタイムを返すか、ということが注釈できていれば良く、textのライフタイムにもなり得るし、default_valのライフタイムにもなり得るということです。

ただし、main()関数からの視点で見てみると、実際には 'a はtextもしくはdefault_valのうち、ライフタイムが短い方に等しいライフタイムになります。
そのため、ライフタイム注釈を書いた状態でビルドしてみると、借用チェッカーにより想定したエラーが出ます。

最後に、main関数を修正して、ビルドが通る状態にします。

ライフタイムの指定が不要なケース

先程の関数を少し変更してみました。

引数default_valの代わりに、separatorとし、文章の区切り文字を引数で渡せるようにしました。また、区切り文字が見つからなかった場合は、渡されたtext全体を1文とみなし、そのまま返すようにしました。(つまり、必ずtextの参照が返ります)
このような場合、sepratorにライフタイム注釈は不要です。なぜなら、separatorがreturnされることがなく、渡ってきた引数textのライフタイムを気にする必要がないためです。

ライフタイムの省略

ところで、これまでの入門編コードでは関数やメソッドで、引数で借用するコードを書いてきましたが、ライフタイム注釈は一切書いてきませんでした。
それには、ライフタイムを省略できる規則があり、その規則通りであればライフタイムはコンパイル時に推論されます。
詳細は、以下のドキュメントが参考になります。

https://doc.rust-jp.rs/book-ja/ch10-03-lifetime-syntax.html#%E3%83%A9%E3%82%A4%E3%83%95%E3%82%BF%E3%82%A4%E3%83%A0%E7%9C%81%E7%95%A5

さいごに

基本的には、ダングリング参照が起きないようなコーディングをした上で、ライフタイム注釈は必要な場面でのみ使えば良いということが分かりました。
Rust特有の概念だと思いますが、基本的にはコンパイラがチェックしてくれるので、コンパイルエラーになった場合には、適宜対応していけば良さそうです。

おすすめ書籍

Webアプリ開発で学ぶ Rust言語入門 パーフェクトRust プログラミングRust 第2版

page_footer_responsive




-BackEnd
-

執筆者:

免責事項

このブログは、記事上部に記載のある投稿日時点の一般的な情報を提供するものであり、投資等の勧誘・法的・税務上の助言を提供するものではありません。仮想通貨の投資・損益計算は複雑であり、個々の取引状況や法律の変更によって異なる可能性があります。ブログに記載された情報は参考程度のものであり、特定の状況に基づいた行動の決定には専門家の助言を求めることをお勧めします。当ブログの情報に基づいた行動に関連して生じた損失やリスクについて、筆者は責任を負いかねます。最新の法律や税務情報を確認し、必要に応じて専門家に相談することをお勧めします。


comment

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA


関連記事

Go言語

[Go初心者]テストについて学習

1 はじめに2 テスト2.1 エラーの値を比較する2.2 エラーの型を比較する3 httptest3.1 httptestの使い方4 さいごに5 おすすめ書籍 はじめに 今回はGo言語のテストについて ...

Go言語

Go言語の基礎〜基本構文その1〜

1 はじめに2 変数2.1 変数の定義2.2 暗黙的な定義2.3 varと暗黙的な定義2.4 ローカル変数とパッケージ変数3 定数3.1 const3.2 iota4 関数4.1 関数定義の基本4.2 ...

rails

Capistrano3でRailsアプリケーションをデプロイする

1 はじめに1.1 前提条件2 Cpistranoについて3 導入3.1 Gemのインストール3.2 設定ファイルの準備4 デプロイ設定4.1 Capfileを修正する4.2 各環境で共通のデプロイ設 ...

Docker上のLaravelのログをFluentdに出力する

1 はじめに2 環境3 Fluentdについて4 目的5 Fluentd本体はdocker-composeで導入5.1 fluentd5.2 db5.3 app5.4 web6 Laravelからログ ...

laravel logo

Laravelでテストコードを書くには? Featureテスト/Unitテスト

1 はじめに2 FeatureとUnitの使い分け3 テスト用データベースの準備4 Featureテスト4.1 テスト対象のコード4.2 テストコードの実装4.3 テスト結果の検証4.3.1 ステータ ...

フォロー

blog-page_side_responsive

2023年8月
 12345
6789101112
13141516171819
20212223242526
2728293031  

アプリ情報

私たちは無料アプリもリリースしています、ぜひご覧ください。 下記のアイコンから無料でダウンロードできます。