怪我の功名から理解できたライフタイム注釈
ライフタイム周りと、型として観察したときの&T
と、&mut T
について色々と理解できたのでまとめておこうかなって
コトの始まり
コレクションの種類にかかわらずIteratorを取得できるようなtraitを作った場合どうなるだろう?というのが、そもそもの始まりだった。
ただ、結果的に今回の定義と実装は、std::slice::Iter
及び、std::slice ::IterMut
に引きずられすぎていて全く妥当ではないので、その点ご注意の程。とは言え、ここを元にして、今回のトピックに気付き、ドツボにハマって理解できたのでその一過程として順序だって説明するには最適かなって。
通常のImmutableなIteratorを返すことを意図したIterable
とMutableなIterator
を返すこと素意としたMutIterable
を以下のように定義した。
pub trait Iterable<'a> {
type Item;
//ここがダメポイント。Itemを参照に固定しちゃってる
type Iter: Iterator<Item = &'a Self::Item>;
fn iter(&self) -> Self::Iter;
}
pub trait MutIterable<'a>: Iterable<'a> {
//同様にダメポイント。
type MutIter: Iterator<Item = &'a mut Self::Item>;
fn iter_mut(&mut self) -> Self::MutIter;
}
定義したのは良いけど、実装したときどうなるのか確認するために、とりあえずBackingStoreがSliceだったとき、まともに作れるのか確認してみた。
Immutableの場合は以下の通り
use super::iterable::Iterable;
use std::slice::Iter as SliceIter;
pub struct SliceIterable<'a, T>(&'a [T]);
impl<'a, T> SliceIterable<'a, T> {
pub fn new(source: &[T]) -> SliceIterable<'_, T> {
SliceIterable(source)
}
}
impl<'a, T: 'a> Iterable<'a> for SliceIterable<'a, T> {
type Item = T;
type Iter = SliceIter<'a, T>;
fn iter(&self) -> Self::Iter {
self.0.iter()
}
}
先の通り使い物にならない設計に更にケチ付けるのもアレだけど、SliceIterable
の'a
と、Iterabel::Item
の'a
は別にすべきだけど、実験目的なので簡易にこんな感じに書いたということで1つ
実際にこいつを使うと以下のようになる。
fn main() {
let vec = vec![1, 2, 3, 4, 5];
let iter = SliceIterable::new(&vec);
print(iter)
}
fn print<'a>(iterable: impl Iterable<'a, Item = i32>) {
for elem in iterable.iter() {
println!("1st:{elem}")
}
println!();
for elem in iterable.iter() {
println!("2nd:{elem}")
}
}
単にIteratorを渡すと1パスしか出来ないけど、任意回数Iteratorを取得できるし、本来的にここを目指していた。
暗雲立ちこめる
次にMutIterable
を&mut [T]
に対して実装したらどうなるのか実験した。MutIterable
はSuper traitとしてIterable
を持つので型と、Iterable
の実装をしていこう(そして失敗する)。
use super::iterable::{Iterable, MutIterable};
use std::slice::{Iter as SliceIter, IterMut as SliceIterMut};
pub struct MutSliceIterable<'a, T>(&'a mut [T]);
impl<'a, T> MutSliceIterable<'a, T> {
pub fn new(source: &'a mut [T]) -> Self {
MutSliceIterable(source)
}
}
impl<'a, T: 'a> Iterable<'a> for MutSliceIterable<'a, T> {
type Item = T;
type Iter = SliceIter<'a, T>;
fn iter(&self) -> Self::Iter {
self.0.iter()
}
}
一見すると、先のSliceIterable
と何ら変わらないので問題無さそうだが、実際には以下のようなコンパイルエラーが発生することになる。
error: lifetime may not live long enough
--> src\mut_slice_iterable.rs:17:3
|
12 | impl<'a, T: 'a> Iterable<'a> for MutSliceIterable<'a, T> {
| -- lifetime `'a` defined here
...
16 | fn iter(&self) -> Self::Iter {
| - let's call the lifetime of this reference `'1`
17 | self.0.iter()
| ^^^^^^^^^^^^^ method was supposed to return data with lifetime `'a` but it is returning data with lifetime `'1`
早い話が、&self
で省略されたライフタイム注釈と、'a
がミスマッチを起こしてるからコンパイル出来ないよと言うことを言われてる。
出てきた疑問
双方は、ほぼ同じ対象に対してほぼ同じ処理を行っている。にもかかわらず片方はコンパイルに成功し、もう片方はコンパイルに失敗する結果を生む。
そもそも、SliceIterable
もMutSliceIterable
のいずれも& 'a [T}
という形で、'a
で修飾されており、返却するAssociate typeのIterもstd::slice::Iterator<'a,T>
となっているので、どちらも'a
で修飾されている。このことから、問題なくライフタイムは解決できると思っていたのに、MutSliceIterable
の方では、&self
のライフタイムに依存する形になりそれがコンパイルエラーを惹起することになっていた。
この部分の解釈がどうにもこうにも不奏功でStack Overflowに質問したりしていた。
出てきた光明
そんな中で、理解の一助になればと思い、1ステップずつ処理を書いてみることにしたら意外なことが解ってきた。まず最初にうまくいってるSliceIterable
の方をバラして書いてみることにしたのが以下(iterのみを抜粋)
fn iter(&self) -> Self::Iter {
//tmoは&'a [T]となってる。
let tmp: &[T] = self.0;
//SliceIter<'a,T>が成立する
let iterator: SliceIter<T> = tmp.iter();
//なのでIter = SliceIter<'a, T>を満たすからコンパイルデキる。
iterator
}
コメントに書いた様な機序で解釈されるのでSliceIterable
はコンパイル可能なのじゃないかなって
他方、MutSliceIterable
はどうだろうか?同様に書いてみた。
fn iter(&self) -> Self::Iter {
let tmp: &[T] = self.0;
let iterator = tmp.iter();
iterator
}
当然これは先と同じコンパイルエラーが発生することになる。
もう少しバラしてみよう。そもそもself.0
は&mut [T]
だったはずである。なので、tmpの型指定を合わせた。
fn iter(&self) -> Self::Iter {
let tmp: &mut [T] = self.0;
let iterator = tmp.iter();
iterator
}
今度は以下のようなコンパイルエラーが発生した
error[E0596]: cannot borrow `*self.0` as mutable, as it is behind a `&` reference
--> src\mut_slice_iterable.rs:17:23
|
17 | let tmp: &mut [T] = self.0;
| ^^^^^^ `self` is a `&` reference, so the data it refers to cannot be borrowed as mutable
|
当然と言えば当然で、&self
経由で、self.0
である&mut [T]
を借用できないよといわれている。じゃあどうすりゃいいのかと言えば、参照の参照をこさえてしまえば解決する
fn iter(&self) -> Self::Iter {
let tmp = &self.0;
let iterator = tmp.iter();
iterator
}
これは一回りしてさっきのコンパイルエラーが発生する
error: lifetime may not live long enough
--> src\mut_slice_iterable.rs:19:3
|
12 | impl<'a, T: 'a> Iterable<'a> for MutSliceIterable<'a, T> {
| -- lifetime `'a` defined here
...
16 | fn iter(&self) -> Self::Iter {
| - let's call the lifetime of this reference `'1`
...
19 | iterator
| ^^^^^^^^ method was supposed to return data with lifetime `'a` but it is returning data with lifetime `'1`
何が起きたのか?
fn iter(&self) -> Self::Iter {
//&&mut [T}のライフタイム注釈は&selfで省略された'_となる。
let tmp:&&mut [T] = &self.0;
//なので此処で取得したイテレータは,SliceIterator<'_,T>となる;
let iterator:SliceIter<T> = tmp.iter();
//なので、この段階で、返却値で制約を化しているSliceIter<'a,T>を満たせない。
iterator
}
コメントに書いたような解釈なんじゃないかなと考えた。
まとめ
設計がまずかったせいで、ライフタイム注釈が複雑化して思いがけず地雷を踏み抜く結果となった。
けれどいろいろなことを深く理解できたのは良かったかなぁと思ってる。
また、普段そんなに気にしていなかった、&T
の束縛が言うところのCopyによってなされ、&mut T
はMoveによってなされるということを再認識した次第でした。
Discussion