【Rust】がばがばRust独学 - 10. Generic Types/Traits/Lifetimes - 3 Lifetimes
Rustの公式ドキュメントを通して、Rustを独学していく記事になります。(がば要素あり)
今回も、Generic Typesについて学びつつ、気になった点をコード確認した結果を記事にしています。
- versions
$ rustc --version rustc 1.40.0 (73528e339 2019-12-16)
Lifetimes
Rustの特徴的なownershipのLifetimesに関してになります。
Validating References with Lifetimes - The Rust Programming Language
ぶら下がり参照の防止
公式ドキュメントのコードより、下記を実行すると、 {}
ブロック内部で x
のスコープが外れます。
そのため、x
が開放されて参照先を無くした r
はエラーとなるべきですので、エラーとして
x
dropped here while still borrowed
となります。
fn main() { { let r; { let x = 5; r = &x; } println!("r: {}", r); } }
error[E0597]: `x` does not live long enough --> src/demo.rs:6:4 | 6 | r = &x; | ^^^^^^ borrowed value does not live long enough 7 | } | - `x` dropped here while still borrowed 8 | println!("r: {}", r); | - borrow later used here
実際のスコープは Validating References with Lifetimes - The Rust Programming Language
公式ドキュメントのボローチェッカー(Validating References with Lifetimes - The Rust Programming Language)がとても分かりやすいです。
関数の一般的な寿命
関数も同様に式(ブロック)内部でのみのLifetimeとなります。
そのため、公式ドキュメントのデモコードのように、longest(x: &str, y: &str) -> &str
とした場合、longest(string1.as_str(), string2)
における string1.as_str()
および string2
は
- どちらが選択されるか返り値(
-> &str
)から分からない &x
、&y
の具体的なLifetimeがlongest
関数から判断が付かない
であるため、コンパイラからの判断が出来ず、エラーとなります。
fn main() { let string1 = String::from("abcd"); let string2 = "xyz"; let result = longest(string1.as_str(), string2); println!("The longest string is {}", result); } fn longest(x: &str, y: &str) -> &str { if x.len() > y.len() { x } else { y } }
error[E0106]: missing lifetime specifier --> src/demo.rs:8:33 | 8 | fn longest(x: &str, y: &str) -> &str { | ^ expected lifetime parameter | = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y`
Lifetime Annotation Syntax
参照の有効期間を変更させないために、アノテーション '
を利用することが出来ます。
デモコードより、関数で利用する場合、 longest
関数において、 x
、 y
、 str
全てのライフタイムが同一でなければならない状態とした場合、 x
、 y
どちらが来たとしても、コンパイラから -> &str
のLifetimeは必ず x
、 y
と同様であることが分かるため、Lifetimeによるエラーはなくなります。
fn main() { let string1 = String::from("abcd"); let string2 = "xyz"; let result = longest(string1.as_str(), string2); println!("The longest string is {}", result); } fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } }
The longest string is abcd
また、下記のように異なるLifetime( longest(string1.as_str(), string2.as_str())
)を渡そうとした場合、 string1
は string2
よりの長いLifetimeであり、 result
のLifetimeは string2
と同一であるためエラーとはなりません。
fn main() { let string1 = String::from("long string is long"); { let string2 = String::from("xyz"); let result = longest(string1.as_str(), string2.as_str()); println!("The longest string is {}", result); } } fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } }
The longest string is long string is long
しかし、下記のように、 result
のLifetimeを string1
と同様にした場合、result
が string2
のLifetime外となってしまうパターンがあるため、エラーとなります。
fn main() { let string1 = String::from("long string is long"); let result; { let string2 = String::from("xyz"); result = longest(string1.as_str(), string2.as_str()); } println!("The longest string is {}", result); } fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } }
error[E0597]: `string2` does not live long enough --> src/demo.rs:6:38 | 6 | result = longest(string1.as_str(), string2.as_str()); | ^^^^^^^ borrowed value does not live long enough 7 | } | - `string2` dropped here while still borrowed 8 | println!("The longest string is {}", result); | ------ borrow later used here
Lifetime Elision
全てのリファレンスにはLifetimeが存在しますが、アノテーション無しでコンパイルできるパターンが存在します。
fn main() { let s = "Hello Rust!"; let fw = first_word(&s); println!("first word -> {}", fw); } fn first_word(s: &str) -> &str { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return &s[0..i]; } } &s[..] }
first word -> Hello
公式ドキュメント(Validating References with Lifetimes - The Rust Programming Language)で背景がしっかりと記載されていますが、ざっくりまとめると以下になります。
- Rustの初期バージョン(1.0以前)は対応していなかった
同一パターンでアノテーションしていたので、特定パターンを省略可能にした
- 省略できるが、完全な推論ではない
関数におけるLifetimeパラメータ名
- 引数:
input lifetime parameter
- 返り値:
output lifetime parameter
- 引数:
3つルールで推論できる場合、省略可能
- ルール1:
input lifetime parameter
が独自のLifetimeを持つ- 例1:
fn foo<'a>(x: &'a i32)
- 例2:
fn foo<'a, 'b>(x: &'a i32, y: &'b i32)
- 例1:
- ルール2:
input lifetime parameter
が1つだけの場合はoutput lifetime parameter
は同一Lifetimeになる- 例:
fn foo<'a>(x: &'a i32) -> &'a i32
- 例:
- ルール3:
input lifetime parameter
が複数の場合はそのうちの一つが&self
、&mut self
、 関数のどれかである場合
- ルール1:
上記のルールより、 first_word(s: &str) -> &str
はルール1、2を満たし、ルール3は適応外のため省略可能となります。
また、 impl
に対してもルール1、3を満たし、ルール2は適応外のため省略可能となります。
impl<'a> ImportantExcerpt<'a> { fn announce_and_return_part(&self, announcement: &str) -> &str { println!("Attention please: {}", announcement); self.part } }
The Static Lifetime
'static
は特別なLifetimeであり、Lifetimeはプログラムの全期間で存続可能です。
let s: &'static str = "I have a static lifetime.";
上記を利用を意識した場合はLifetimeが不要なのか確認する必要があります。長期間保持する必要があるのかどうか・・・
最後に
Lifetimeは省略系を除けば、Ownershipの深堀り的な内容ですね!
String
と usize
のLifetimeに気をつけたりすれば、かなり有効的に使えますね。
体に染み込ませるまでに時間がかかりそうではありますが、使いこなせるようになって、Rustのメリットを活かしたいですね。
次はテスト。DevOpsでも重要になってくるテストですね。