ライフタイム

このガイドはRustの所有権システムの3つの解説の3つ目です。 これはRustの最も独特で注目されている機能です。そして、Rust開発者はそれについて高度に精通しておくべきです。 所有権こそはRustがその最大の目標、メモリ安全性を得るための方法です。 そこにはいくつかの別個の概念があり、各概念が独自の章を持ちます。

それらの3つの章は関連していて、それらは順番に並んでいます。 所有権システムを完全に理解するためには、3つ全てを必要とするでしょう。

概論

詳細に入る前に、所有権システムについての2つの重要な注意があります。

Rustは安全性とスピードに焦点を合わせます。 Rustはそれらの目標をたくさんの「ゼロコスト抽象化」を通じて成し遂げます。それは、Rustでは抽象化を機能させるためのコストをできる限り小さくすることを意味します。 所有権システムはゼロコスト抽象化の主な例です。 このガイドの中で話すであろう解析の全ては コンパイル時に行われます 。 それらのどの機能に対しても実行時のコストは全く掛かりません。

しかし、このシステムはあるコストを持ちます。それは学習曲線です。 多くの新しいRustのユーザは「借用チェッカとの戦い」と好んで呼ばれるものを経験します。そこではRustコンパイラが開発者が正しいと考えるプログラムをコンパイルすることを拒絶します。 所有権がどのように機能するのかについてのプログラマのメンタルモデルがRustの実装する実際のルールにマッチしないため、これはしばしば起きます。 しかし、よいニュースがあります。より経験豊富なRustの開発者は次のことを報告します。一度彼らが所有権システムのルールとともにしばらく仕事をすれば、彼らが借用チェッカと戦うことは少なくなっていくということです。

それを念頭に置いて、ライフタイムについて学びましょう。

ライフタイム

他の誰かの所有するリソースへの参照の貸付けは複雑になることがあります。 例えば、次のような一連の作業を想像しましょう。

  1. 私はある種のリソースへのハンドルを取得する
  2. 私はあなたにリソースへの参照を貸し付ける
  3. 私はリソースを使い終わり、それを解放することを決めるが、あなたはそれに対する参照をまだ持っている
  4. あなたはリソースを使うことを決める

あー!  あなたの参照は無効なリソースを指示しています。 リソースがメモリであるとき、これはダングリングポインタ又は「解放後の使用」と呼ばれます。

これを修正するためには、ステップ3の後にステップ4が絶対に起こらないようにしなければなりません。 Rustでの所有権システムはこれをライフタイムと呼ばれる概念を通じて行います。それは参照の有効なスコープを記述するものです。

引数として参照を受け取る関数について、参照のライフタイムを黙示又は明示することができます。

fn main() { // implicit // 黙示的に fn foo(x: &i32) { } // explicit // 明示的に fn bar<'a>(x: &'a i32) { } }
// 黙示的に
fn foo(x: &i32) {
}

// 明示的に
fn bar<'a>(x: &'a i32) {
}

'aは「ライフタイムa」と読みます。 技術的には参照は全てそれに関連するライフタイムを持ちますが、一般的な場合にはコンパイラがそれらを省略してもよいように計らってくれます(つまり、「省略」できるということです。 「ライフタイムの省略」 以下を見ましょう)。 しかし、それに入る前に、明示の例を分解しましょう。

fn main() { fn bar<'a>(...) }
fn bar<'a>(...)

関数の構文 については前に少し話しました。しかし、関数名の後の <> については議論しませんでした。 関数は <> の間に「ジェネリックパラメータ」を持つことができ、ライフタイムはその一種です。 他の種類のジェネリクスについては 本書の後の方 で議論しますが、とりあえず、ライフタイムの面だけに焦点を合わせましょう。

<> はライフタイムを宣言するために使われます。 これは bar が1つのライフタイム 'a を持つことを意味します。 もし2つの参照引数があれば、それは次のような感じになるでしょう。

fn main() { fn bar<'a, 'b>(...) }
fn bar<'a, 'b>(...)

そして引数リストでは、名付けたライフタイムを使います。

fn main() { ...(x: &'a i32) }
...(x: &'a i32)

もし &mut 参照が欲しいのならば、次のようにします。

fn main() { ...(x: &'a mut i32) }
...(x: &'a mut i32)

もし &mut i32&'a mut i32 と比較するならば、それらは同じです。それはライフタイム 'a&mut i32 の間にこっそり入っているだけです。 &mut i32 は「 i32 へのミュータブルな参照」のように読み、 &'a mut i32 は「ライフタイム 'a を持つ i32 へのミュータブルな参照」のように読みます。

struct の中

参照を含む struct を使うときにも、明示的なライフタイムを必要とするでしょう。

struct Foo<'a> { x: &'a i32, } fn main() { // let y = &5; // this is the same as `let _y = 5; let y = &_y;` let y = &5; // これは`let _y = 5; let y = &_y;`と同じ let f = Foo { x: y }; println!("{}", f.x); }
struct Foo<'a> {
    x: &'a i32,
}

fn main() {
    let y = &5; // これは`let _y = 5; let y = &_y;`と同じ
    let f = Foo { x: y };

    println!("{}", f.x);
}

見てのとおり、 struct もライフタイムを持つことができます。 これは関数と同じ方法です。

fn main() { struct Foo<'a> { x: &'a i32, } }
struct Foo<'a> {

このようにライフタイムを宣言します。

fn main() { struct Foo<'a> { x: &'a i32, } }
x: &'a i32,

そしてそれを使います。 それではなぜここでライフタイムを必要とするのでしょうか。 Foo への全ての参照がそれの含む i32 への参照より長い間有効にはならないことを保証する必要があるからです。

impl ブロック

Foo に次のようなメソッドを実装しましょう。

struct Foo<'a> { x: &'a i32, } impl<'a> Foo<'a> { fn x(&self) -> &'a i32 { self.x } } fn main() { // let y = &5; // this is the same as `let _y = 5; let y = &_y;` let y = &5; // これは`let _y = 5; let y = &_y;`と同じ let f = Foo { x: y }; println!("x is: {}", f.x()); }
struct Foo<'a> {
    x: &'a i32,
}

impl<'a> Foo<'a> {
    fn x(&self) -> &'a i32 { self.x }
}

fn main() {
    let y = &5; // これは`let _y = 5; let y = &_y;`と同じ
    let f = Foo { x: y };

    println!("x is: {}", f.x());
}

見てのとおり、 Foo のライフタイムは impl 行で宣言する必要があります。 ちょうど関数のときのように 'a は2回繰り返されます。つまり、 impl<'a> はライフタイム 'a を定義し、 Foo<'a> はそれを使うのです。

複数のライフタイム

もし複数の参照があれば、同じライフタイムを複数回使うことができます。

fn main() { fn x_or_y<'a>(x: &'a str, y: &'a str) -> &'a str { x } }
fn x_or_y<'a>(x: &'a str, y: &'a str) -> &'a str {

これは xy が両方とも同じスコープで有効であり、戻り値もそのスコープで有効であることを示します。 もし xy に違うライフタイムを持たせたいのであれば、複数のライフタイムパラメータを使うことができます。

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

この例では xy が異なる有効なスコープを持ちますが、戻り値は x と同じライフタイムを持ちます。

スコープの考え方

ライフタイムについて考えるには、参照の有効なスコープを可視化することです。 例えばこうです。

fn main() { // let y = &5; // -+ y goes into scope // // | // // stuff // | // // | // } // -+ y goes out of scope let y = &5; // -+ yがスコープに入る // | // stuff // | // | } // -+ yがスコープから出る
fn main() {
    let y = &5;     // -+ yがスコープに入る
                    //  |
    // stuff        //  |
                    //  |
}                   // -+ yがスコープから出る

Foo を追加するとこうなります。

struct Foo<'a> { x: &'a i32, } fn main() { // let y = &5; // -+ y goes into scope // let f = Foo { x: y }; // -+ f goes into scope // // stuff // | // // | // } // -+ f and y go out of scope let y = &5; // -+ yがスコープに入る let f = Foo { x: y }; // -+ fがスコープに入る // stuff // | // | } // -+ fとyがスコープから出る
struct Foo<'a> {
    x: &'a i32,
}

fn main() {
    let y = &5;           // -+ yがスコープに入る
    let f = Foo { x: y }; // -+ fがスコープに入る
    // stuff              //  |
                          //  |
}                         // -+ fとyがスコープから出る

fy のスコープの中で有効なので、全て動きます。 もしそれがそうではなかったらどうでしょうか。 このコードは動かないでしょう。

struct Foo<'a> { x: &'a i32, } fn main() { // let x; // -+ x goes into scope // // | // { // | // let y = &5; // ---+ y goes into scope // let f = Foo { x: y }; // ---+ f goes into scope // x = &f.x; // | | error here // } // ---+ f and y go out of scope // // | // println!("{}", x); // | // } // -+ x goes out of scope let x; // -+ xがスコープに入る // | { // | let y = &5; // ---+ yがスコープに入る let f = Foo { x: y }; // ---+ fがスコープに入る x = &f.x; // | | ここでエラーが起きる } // ---+ fとyがスコープから出る // | println!("{}", x); // | } // -+ xがスコープから出る
struct Foo<'a> {
    x: &'a i32,
}

fn main() {
    let x;                    // -+ xがスコープに入る
                              //  |
    {                         //  |
        let y = &5;           // ---+ yがスコープに入る
        let f = Foo { x: y }; // ---+ fがスコープに入る
        x = &f.x;             //  | | ここでエラーが起きる
    }                         // ---+ fとyがスコープから出る
                              //  |
    println!("{}", x);        //  |
}                             // -+ xがスコープから出る

ふう! 見てのとおり、ここでは fy のスコープは x のスコープよりも小さいです。 しかし x = &f.x を実行するとき、 x をまさにスコープから外れた何かの参照にしてしまいます。

名前の付いたライフタイムはそれらのスコープに名前を与える方法です。 何かに名前を与えることはそれについて話をすることができるようになるための最初のステップです。

'static

「static」と名付けられたライフタイムは特別なライフタイムです。 それは何かがプログラム全体に渡るライフタイムを持つことを示します。 ほとんどのRustのプログラマが最初に 'static に出会うのは、文字列を扱うときです。

fn main() { let x: &'static str = "Hello, world."; }
let x: &'static str = "Hello, world.";

文字列リテラルは &'static str 型を持ちます。なぜなら、参照が常に有効だからです。それらは最終的なバイナリのデータセグメントに焼き付けられます。 もう1つの例はグローバルです。

fn main() { static FOO: i32 = 5; let x: &'static i32 = &FOO; }
static FOO: i32 = 5;
let x: &'static i32 = &FOO;

これはバイナリのデータセグメントに i32 を追加します。そして、 x はそれへの参照です。

ライフタイムの省略

Rustは関数本体の部分では強力なローカル型推論をサポートします。しかし要素のシグネチャの部分では、型が要素のシグネチャだけでわかるようにするため、(型推論が)許されていません。 とはいえ、エルゴノミック(人間にとっての扱いやすさ)の理由により、"ライフタイムの省略"と呼ばれている、非常に制限された第二の推論アルゴリズムがシグネチャの部分に適用されます。 その推論はシグネチャのコンポーネントだけに基づき、関数本体には基づかず、ライフタイムパラメータだけを推論します。そしてたった3つの覚えやすく明確なルールに従って行います。 ライフタイムの省略で要素のシグネチャを短く書くことができます。しかしローカル型推論が適用されるときのように実際の型を隠すことはできません。

ライフタイムの省略について話すときには、 入力ライフタイム出力ライフタイム という用語を使います。 入力ライフタイム は関数の引数に関連するライフタイムで、 出力ライフタイム は関数の戻り値に関連するライフタイムです。 例えば、次の関数は入力ライフタイムを持ちます。

fn main() { fn foo<'a>(bar: &'a str) }
fn foo<'a>(bar: &'a str)

この関数は出力ライフタイムを持ちます。

fn main() { fn foo<'a>() -> &'a str }
fn foo<'a>() -> &'a str

この関数は両方の位置のライフタイムを持ちます。

fn main() { fn foo<'a>(bar: &'a str) -> &'a str }
fn foo<'a>(bar: &'a str) -> &'a str

これが3つのルールです。

そうでないときは、出力ライフタイムの省略はエラーです。

ここにライフタイムの省略された関数の例を示します。 省略されたライフタイムの各例をその展開した形式と組み合わせています。

fn main() { // fn print(s: &str); // elided // fn print<'a>(s: &'a str); // expanded fn print(s: &str); // 省略された形 fn print<'a>(s: &'a str); // 展開した形 // fn debug(lvl: u32, s: &str); // elided // fn debug<'a>(lvl: u32, s: &'a str); // expanded fn debug(lvl: u32, s: &str); // 省略された形 fn debug<'a>(lvl: u32, s: &'a str); // 展開された形 // In the preceding example, `lvl` doesn’t need a lifetime because it’s not a // reference (`&`). Only things relating to references (such as a `struct` // which contains a reference) need lifetimes. // 前述の例では`lvl`はライフタイムを必要としません。なぜなら、それは参照(`&`) // ではないからです。(参照を含む`struct`のような)参照に関係するものだけがライ // フタイムを必要とします。 // fn substr(s: &str, until: u32) -> &str; // elided // fn substr<'a>(s: &'a str, until: u32) -> &'a str; // expanded fn substr(s: &str, until: u32) -> &str; // 省略された形 fn substr<'a>(s: &'a str, until: u32) -> &'a str; // 展開された形 // fn get_str() -> &str; // ILLEGAL, no inputs fn get_str() -> &str; // 不正。入力がない // fn frob(s: &str, t: &str) -> &str; // ILLEGAL, two inputs // fn frob<'a, 'b>(s: &'a str, t: &'b str) -> &str; // Expanded: Output lifetime is ambiguous fn frob(s: &str, t: &str) -> &str; // 不正。入力が2つある fn frob<'a, 'b>(s: &'a str, t: &'b str) -> &str; // 展開された形。出力ライフタイムが決まらない // fn get_mut(&mut self) -> &mut T; // elided // fn get_mut<'a>(&'a mut self) -> &'a mut T; // expanded fn get_mut(&mut self) -> &mut T; // 省略された形 fn get_mut<'a>(&'a mut self) -> &'a mut T; // 展開された形 // fn args<T:ToCStr>(&mut self, args: &[T]) -> &mut Command; // elided // fn args<'a, 'b, T:ToCStr>(&'a mut self, args: &'b [T]) -> &'a mut Command; // expanded fn args<T:ToCStr>(&mut self, args: &[T]) -> &mut Command; // 省略された形 fn args<'a, 'b, T:ToCStr>(&'a mut self, args: &'b [T]) -> &'a mut Command; // 展開された形 // fn new(buf: &mut [u8]) -> BufWriter; // elided // fn new<'a>(buf: &'a mut [u8]) -> BufWriter<'a>; // expanded fn new(buf: &mut [u8]) -> BufWriter; // 省略された形 fn new<'a>(buf: &'a mut [u8]) -> BufWriter<'a>; // 展開された形 }
fn print(s: &str); // 省略された形
fn print<'a>(s: &'a str); // 展開した形

fn debug(lvl: u32, s: &str); // 省略された形
fn debug<'a>(lvl: u32, s: &'a str); // 展開された形

// 前述の例では`lvl`はライフタイムを必要としません。なぜなら、それは参照(`&`)
// ではないからです。(参照を含む`struct`のような)参照に関係するものだけがライ
// フタイムを必要とします。

fn substr(s: &str, until: u32) -> &str; // 省略された形
fn substr<'a>(s: &'a str, until: u32) -> &'a str; // 展開された形

fn get_str() -> &str; // 不正。入力がない

fn frob(s: &str, t: &str) -> &str; // 不正。入力が2つある
fn frob<'a, 'b>(s: &'a str, t: &'b str) -> &str; // 展開された形。出力ライフタイムが決まらない

fn get_mut(&mut self) -> &mut T; // 省略された形
fn get_mut<'a>(&'a mut self) -> &'a mut T; // 展開された形

fn args<T:ToCStr>(&mut self, args: &[T]) -> &mut Command; // 省略された形
fn args<'a, 'b, T:ToCStr>(&'a mut self, args: &'b [T]) -> &'a mut Command; // 展開された形

fn new(buf: &mut [u8]) -> BufWriter; // 省略された形
fn new<'a>(buf: &'a mut [u8]) -> BufWriter<'a>; // 展開された形