やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

Rustのライフタイム3(ライフタイム省略)

 引数がひとつであり、その参照を返すとき、引数と戻り値のライフタイム注釈は省略できる。

成果物

参考

ライフタイム省略

 以下の関数はコンパイルできる。参照を返すにも関わらずライフタイム注釈していないのに。

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[..]
}

 コンパイルできた理由は、ライフタイム省略規則でそのように定められているから。Rust1.0以前は以下のように書かねばならなかった。

fn first_word<'a>(s: &'a str) -> &'a str {

ライフタイム省略規則

  1. 参照である各引数は、独自のライフタイム引数を得る
  2. 1つだけ入力ライフタイム引数があるなら、そのライフタイムが全ての出力ライフタイム引数に代入される
  3. 複数の入力ライフタイム引数があるが、メソッド引数のうちの一つが&self&mut selfなら、selfのライフタイムが全出力ライフタイム引数に代入される

 これら3つのルールをすべて適用することでライフタイム省略する。この規則だけではライフタイムが判明しないとき、ライフタイム注釈にて明示する。さもなくばコンパイルエラー。

規則 befor after
1 fn foo(x: &i32, y: &i32) fn foo<'a, 'b>(x: &'a i32, y: &'b i32)
2 fn foo(x: &i32) -> &i32 fn foo<'a>(x: &'a i32) -> &'a i32
3 impl S { fn foo(&self, p1: &i32) -> &i32 {} } impl<'a> S<'a> { fn foo<'a>(&'a self, p1: &'b i32) -> &'a i32 {} }

省略シーケンスA

 以下の関数を定義したとき、3つの省略規則を順にあてはめていく。

fn first_word(s: &str) -> &str {

1. 参照である各引数は、独自のライフタイム引数を得る

fn first_word(s: &'a str) -> &str {

2. 1つだけ入力ライフタイム引数があるなら、そのライフタイムが全ての出力ライフタイム引数に代入される

fn first_word(s: &'a str) -> &'a str {

3. 複数の入力ライフタイム引数があるが、メソッド引数のうちの一つが&self&mut selfなら、selfのライフタイムが全出力ライフタイム引数に代入される

 これは適用されない。メソッドではなく、引数に&self, &mut selfがないから。

 結果、戻り値のライフタイムは引数と同じであることが自動的に示された。これにてコンパイルエラーになることもなく、ライフタイムを省略できた。

省略ケースB

 おなじく以下の関数を定義したとき、3つの省略規則を順にあてはめていく。

fn longest(x: &str, y: &str) -> &str {

1. 参照である各引数は、独自のライフタイム引数を得る

fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str {

 'a, 'bというそれぞれ独自のライフタイムを得る。

2. 1つだけ入力ライフタイム引数があるなら、そのライフタイムが全ての出力ライフタイム引数に代入される

 適用されない。ライフタイムを持った引数が1つだけではなく、2つあるため対象外。

3. 複数の入力ライフタイム引数があるが、メソッド引数のうちの一つが&self&mut selfなら、selfのライフタイムが全出力ライフタイム引数に代入される

 適用されない。メソッドではなく、引数に&self, &mut selfがないから。

 戻り値のライフタイムが不明のままである。よってコンパイルエラーとなる。ライフタイム省略規則だけでは判明しないため、ライフタイム注釈による明示が必要。

省略ケースC

 おなじく以下のimplを定義したとき、3つの省略規則を順にあてはめていく。

impl<'a> ImportantExcerpt<'a> {
    fn level(&self) -> i32 { 3 }
}

 メソッド実装時はこのようにライフタイム注釈する。

1. 参照である各引数は、独自のライフタイム引数を得る

impl<'a> ImportantExcerpt<'a> {
    fn level(&'a self) -> i32 { 3 }
}

 2, 3番目は適用されない。

 そもそも戻り値は参照ではないため、ライフタイムについて考慮する必要がない。戻り値の所有権は、戻り値を受け取る側にムーブされる。戻り値のライフタイムは受け取った側のスコープとなる。

省略ケースD

impl<'a> ImportantExcerpt<'a> {
    fn announce_and_return_part(&self, announcement: &str) -> &str {
        println!("Attention please: {}", announcement);
        self.part
    }
}

1. 参照である各引数は、独自のライフタイム引数を得る

impl<'a> ImportantExcerpt<'a> {
    fn announce_and_return_part(&'a self, announcement: &'b str) -> &str {
        println!("Attention please: {}", announcement);
        self.part
    }
}

 第一引数に'a, 第二引数に'bとそれぞれ独自のライフタイムを得る。

2. 1つだけ入力ライフタイム引数があるなら、そのライフタイムが全ての出力ライフタイム引数に代入される

 適用されない。ライフタイムを持った引数が1つだけではなく、2つあるため対象外。

3. 複数の入力ライフタイム引数があるが、メソッド引数のうちの一つが&self&mut selfなら、selfのライフタイムが全出力ライフタイム引数に代入される

impl<'a> ImportantExcerpt<'a> {
    fn announce_and_return_part(&'a self, announcement: &'b str) -> &'a str {
        println!("Attention please: {}", announcement);
        self.part
    }
}

 戻り値のライフタイムは&selfとおなじ'aとなる。

 戻り値のライフタイムが判明したので、コンパイルエラーにならず成功する。

対象環境

$ uname -a
Linux raspberrypi 4.19.42-v7+ #1219 SMP Tue May 14 21:20:58 BST 2019 armv7l GNU/Linux

前回まで