飴屋

Rust/ライフタイム

あんまり気にしたくなかったライフタイム

Rustに関して、コンパイルの段階で大方の問題はコンパイラが気づいて教えてくれるので「なんて便利な子!?」ってかわいがってきてたんですが、unsafe な文脈になると途端に自己責任を押し付けられてかわいげがなくなるので、私はまだ一回も「unsafe」を書いたことがないです。効率を求めなければ、それでも問題なく書けるので特に不便ではないのでした。

unsafeと同じように煩わしそうにみえたのがライフタイムでした。よくわかんないけどたまに 'static とか 'a とかくっついてくるあいつです。こいつもこれまで使わないように頑張って書こうと思えば書けたので、利用に積極的にはなれなかったのですが、頑張りにも限界を感じてしまい重い腰をあげたのでした。

ライフタイム導入以前

私に必要だったのは構造体のフィールドのライフタイムでした。あれ、Rustはフィールドでいいんだっけ?プロパティ?アトリビュート?パラメータ?とりあえず、以下のようなサンプルで考えましょう。

struct Curry {}
impl Curry {
  fn cook(&self,spice:&Spice) {}
}
struct Spice {
  cumin: f64,
  turmeric: f64,
}
fn main() {
  let curry = Curry{};
  let spice = Spice{ cumin:0.5, turmeric:0.5 };
  curry.cook(&spice);
}

カレー(Curry)の調理(cook)にスパイス(Spice)を使うとします。

struct Chicken {}
impl Chicken {
  fn cook(&self,spice:&Spice) {}
}

fn main() {
  // 前略
  let chicken = Curry{};
  chicken.cook(&spice);
}

同じスパイスでタンドリーチキン(Chiken)も作りたくなりました。

impl Curry {
  fn cook(&self,spice:&Spice) {
    self.cook_mutton(spice);
    self.cook_source(spice);
    self.cook_mutton(spice);
    self.cook_source(spice);
  }
  fn cook_mutton(&self,spice:&Spice) {}
  fn cook_source(&self,spice:&Spice) {}
}

カレーの調理工程が複雑化し、羊肉の調理(cook_mutton)やカレールーの調理(cook_source)が頻発するようになりました。いずれの工程でもスパイスを使います。引数にスパイスを渡し続ければいいだけなのでカレーはできあがりますが、調理工程が複雑化する度にスパイスを渡し続ける毎日です。

スパイスのフィールド化

struct Curry {
  spice: Spice,
}
impl Curry {
  fn cook(&self) {
    self.cook_mutton();
    self.cook_source();
    self.cook_mutton();
    self.cook_source();
  }
  fn cook_mutton(&self) {}
  fn cook_source(&self) {}
}
fn main() {
  let spice = Spice{ cumin:0.5, turmeric:0.5 };
  let curry = Curry{ spice };
  curry.cook(&spice);
  // 後略
}

カレーがフィールドにスパイスを持ってれば、それを使って調理するように書けるぞ!って思いますが「後略」部分でタンドリーチキンも作ってるので、カレーにスパイスを独占されちゃうと困ってしまいます。タンドリーチキン用に同じスパイスを買ってくるのは経済的ではないです。うちは富豪じゃないという前提です。

struct Curry {
  spice: &Spice,
}

&をつけてスパイスは借用することにしましょう。しかしここでコンパイラは警告を発してくるのでした。借りてきたスパイスは所有権がない、すなわち借りようとしたときに実物があるかどうか自分で保証できないのでした。これがスパイスのライフタイムか。スパイスなんて長持ちしそうですが、保管方法が悪かったり、ものによっては新鮮さが求められたりしますもんね。コンパイラがある程度ライフライムを類推してくれることもあるみたいですが、自分でライフタイムは管理しておかないと思わぬ事故につながりかねませんね。

ライフタイム導入以降

struct Curry {
  spice: &'a Spice,
}
impl<'a> Curry<'a> {
  fn cook(&self) {
    self.cook_mutton();
    self.cook_source();
    self.cook_mutton();
    self.cook_source();
  }
  fn cook_mutton(&self) {}
  fn cook_source(&self) {}
}

カレーのスパイスにライフタイム('a)をくっつけました。それに伴いスパイスを使う実装も「impl<'a> Curry<'a> {}」と書き換わりました。この実装の中ではスパイスのライフタイム('a)が保証されるってわけですね。単純な例だったせいかライフタイムはあっさり導入できちゃいました。これまで身構えていたのは何だったのでしょう。複数のライフタイムが関わってきたりするとまたちょっとややこしいのかもしれませんが、今はこれで満足しておきましょう。

ちなみにミュータブルな参照にライフタイムを付ける場合は「&'a mut Spice」みたいな書き順になるそうです。修飾が増えるほどとっつきづらく感じちゃいます。