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版

blog-page_footer_336




blog-page_footer_336




-BackEnd
-

執筆者:

免責事項

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


comment

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

CAPTCHA


関連記事

Go言語

Go 1.23 でslicesとmapsに追加された関数

はじめに 前回の記事では Go 1.23 で追加された iter パッケージについて紹介しましたが、Go 1.23では slices パッケージと maps パッケージにも関連する関数が追加されました ...

Go言語

Go言語、ゴルーチン(goroutine)で並列処理を

はじめに こんにちは、nukkyです。Go連載7回目を担当させていただきます。 プログラムを書く上ではなかなか避けては通れない非同期処理について今回は記事にしたいと思います。 ゴルーチン Goにはスレ ...

laravel logo

Server-Side Eventsを触ってみた

はじめに 最近リアルタイム通信を行う事が出来るServer-Side Events (SSE)という技術について触れる事があったので、紹介したいと思います。 Server-Side Events(SS ...

rails

Shrineを使って画像をアップロードする

はじめに こんにちは、onoです。 以前、CarrierWaveを使って画像をアップロードするフォームを作成しました。 CarrierWaveは多機能で使いやすいGemだと思いますが、コードが大きく複 ...

laravel logo

Laravelで画像アップロード実装が楽になるかもしれないlaravel-imageup

はじめに こんにちは。webアプリケーションを作る際にファイルアップロードはごく当然の機能で、Laravelでももちろん標準でサポートされています。が、今回は要件次第では実装がほとんど不要になるライブ ...

フォロー

blog-page_side_responsive

2023年8月
日 月 火 水 木 金 土
 12345
6789101112
13141516171819
20212223242526
2728293031  

アプリ情報

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