やってみる

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

Rustの高度な機能(ライフタイム)

 3つの機能について。

成果物

参照

3つの機能

  • ライフタイム・サブタイピング: あるライフタイムが他のライフタイムより長生きすることを保証する
  • ライフタイム境界: ジェネリックな型への参照のライフタイムを指定する
  • トレイトオブジェクトのライフタイムの推論: コンパイラにトレイトオブジェクトのライフタイムを推論させることと指定する必要があるタイミング

ライフタイム・サブタイピング

 ライフタイム・サブタイピングは、あるライフタイムが他のライフタイムより長生きすべきであることを指定する。

struct Context(&str);
struct Parser {
    context: &Context,
}
impl Parser {
    fn parse(&self) -> Result<(), &str> {
        Err(&self.context.0[1..])
    }
}
fn main() {
    println!("Hello Rust !!");
}
$ rustc main.rs
error[E0106]: missing lifetime specifier
 --> main.rs:5:16
  |
5 | struct Context(&str);
  |                ^ expected lifetime parameter

error[E0106]: missing lifetime specifier
 --> main.rs:7:14
  |
7 |     context: &Context,
  |              ^ expected lifetime parameter

 参照にライフタイム指定子を与えてコンパイル可にしたコードは以下。

struct Context<'a>(&'a str);
struct Parser<'a> {
    context: &'a Context<'a>,
}
impl<'a> Parser<'a> {
    fn parse(&self) -> Result<(), &str> {
        Err(&self.context.0[1..])
    }
}

 parse_context関数を追加すると以下エラー。

fn parse_context(context: Context) -> Result<(), &str> {
    Parser { context: &context }.parse()
}
$ rustc main.rs
error[E0597]: borrowed value does not live long enough
  --> main.rs:18:5
   |
18 |     Parser { context: &context }.parse()
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ temporary value does not live long enough
19 | }
   | - temporary value only lives until here
   |
note: borrowed value must be valid for the anonymous lifetime #1 defined on the function body at 17:1...
  --> main.rs:17:1
   |
17 | / fn parse_context(context: Context) -> Result<(), &str> {
18 | |     Parser { context: &context }.parse()
19 | | }
   | |_^

error[E0597]: `context` does not live long enough
  --> main.rs:18:24
   |
18 |     Parser { context: &context }.parse()
   |                        ^^^^^^^ borrowed value does not live long enough
19 | }
   | - borrowed value only lives until here
   |
note: borrowed value must be valid for the anonymous lifetime #1 defined on the function body at 17:1...
  --> main.rs:17:1
   |
17 | / fn parse_context(context: Context) -> Result<(), &str> {
18 | |     Parser { context: &context }.parse()
19 | | }
   | |_^

 生成されたParsercontextparse_context関数内までしか生存しない。

 原因を探る。parseメソッドについて見てみる。ライフタイム省略規則により、以下の意味となる。

fn parse(&self) -> Result<(), &str> {
fn parse<'a>(&'a self) -> Result<(), &'a str> {

 parseが返すResultParse構造体インスタンスと同じである。

 再びparse_context関数をみてみると、同関数内でParserインスタンスを生成している。つまり関数終了時にライフタイム期間終了となり死ぬ。それを返しているため、上記のコンパイルエラーとなっている。

 

struct Context<'s>(&'s str);
struct Parser<'c, 's> {
    context: &'c Context<'s>,
}
impl<'c, 's> Parser<'c, 's> {
    fn parse(&self) -> Result<(), &'s str> {
        Err(&self.context.0[1..])
    }
}
fn parse_context(context: Context) -> Result<(), &str> {
    Parser { context: &context }.parse()
}

 ドキュメントではerror[E0491]: in type&'c Context<'s>, reference has a longer lifetime than the data it referencesになるとある。だが、私の環境では以下警告が出ただけ。

warning: struct is never constructed: `Context`
 --> main.rs:8:1
  |
8 | struct Context<'s>(&'s str);
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: #[warn(dead_code)] on by default

warning: struct is never constructed: `Parser`
 --> main.rs:9:1
  |
9 | struct Parser<'c, 's> {
  | ^^^^^^^^^^^^^^^^^^^^^

warning: method is never used: `parse`
  --> main.rs:13:5
   |
13 |     fn parse(&self) -> Result<(), &'s str> {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

warning: function is never used: `parse_context`
  --> main.rs:17:1
   |
17 | fn parse_context(context: Context) -> Result<(), &str> {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

 ライフタイム'sが少なくとも'cと同じ期間だけ生きることを指定するコードは以下。

struct Parser<'c, 's: 'c> {
    context: &'c Context<'s>,
}

ライフタイム境界

 ライフタイム境界とは、ジェネリックな型へライフタイム引数を追加することである。ジェネリック型内部の参照が参照先データよりも長生きしないことを確かめる。

struct Ref<'a, T>(&'a T);

 ドキュメントではerror[E0309]: the parameter typeTmay not live long enoughになるようだが、私の環境ではエラーにならない。

 ドキュメントでは、上記エラーを以下で解決できるとある。

struct Ref<'a, T: 'a>(&'a T);

 'staticにもできる。(プログラム終了まで生存)

struct StaticRef<T: 'static>(&'static T);

トレイとオブジェクトのライフタイム推論

trait Red { }
struct Ball<'a> {
    diameter: &'a i32,
}
impl<'a> Red for Ball<'a> { }
fn main() {
    let num = 5;
    let obj = Box::new(Ball { diameter: &num }) as Box<Red>;
}

objに関連するライフタイムを注釈していないものの、このコードはエラーなくコンパイルできます。 ライフタイムとトレイトオブジェクトと共に働く規則があるので、このコードは動くのです:

  • トレイトオブジェクトのデフォルトのライフタイムは、'static
  • &'a Trait&'a mut Traitに関して、トレイトオブジェクトのデフォルトのライフタイムは'a
  • 単独のT: 'a節について、トレイトオブジェクトのデフォルトのライフタイムは、'a
  • 複数のT: 'aのような節について、デフォルトのライフタイムはない; 明示しなければならない

 明示せねばならないとき、以下の記法にてライフタイム境界を追加できる。

  • Box<Red + 'static>
  • Box<Red + 'a>

 ドキュメントにはコード例がなかった。どう書けばいいのかわからない。予想すると以下。

    let obj: Box<Red + 'a> = Box::new<'a>(Ball<'a> { diameter: &'a num }) as Box<Red + 'a>;

対象環境

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

前回まで