【Rust】ライフタイムとVariance


# 対象読者

以下のコードがコンパイルエラーになる理由がわからない方。

```rust
struct Hoge<'a> {
    i: &'a usize
}

fn func<'a>(_v: &'a mut Hoge<'a>) {
    // noop
}

fn main() {
    let i = 1;
    let mut hoge = Hoge{i: &i};

    func(&mut hoge); // OK
    func(&mut hoge); // ERROR
}
```

# はじめに

この記事ではRustのライフタイムとVarianceについて例を示し、最後に上記のコードがなぜコンパイルが通らないかについて考える。

# サブタイプとVariance

https://doc.rust-lang.org/nightly/nomicon/subtyping.html

まずそもそも「Varianceとはなんぞや」という方は上記記事をおすすめする。( [日本語版は未訳](https://doc.rust-jp.rs/rust-nomicon-ja/subtyping.html)のようだ)
サブタイプとVarianceについてわかりやすく書かれているが、ライフタイムについてはそれほど詳しく書かれているわけではない。

以下は上記記事にあるVariance早見表である。

||'a|T|U|
|--- |--- |--- |--- |
|&'a T|covariant|covariant||
|&'a mut T|covariant|invariant||
|Box<T>||covariant||
|Vec<T>||covariant||
|UnsafeCell<T>||invariant||
|Cell<T>||invariant||
|fn(T) -> U||contravariant|covariant|
|*const T||covariant||
|*mut T||invariant||

うっかりすると「ライフタイムにはcovariantしかないのだな」と勘違いしてしまうがそうではない。
注意すべきは**TやUがライフタイムを含んでいる場合T,Uと同じVarianceとなる** [^1] ということである。
例を示すと関数の引数に`&'a mut Hoge<'b>`このような指定が合った場合`'b`は`&mut T`の`T`と同様invariantになるということである。

[^1]: 厳密には必ずしも同じとはならないのだが本記事では同じとなる例しかでてこない
 
# ライフタイムとVariance

型に関するサブタイピングのVarianceを考える機会というのはそう多くはない（個人の感想です）が、 **Rustではライフタイムにもサブタイプが存在するため、Varianceについて頻繁に接する**こととなる。例を見ていこう。

## ライフタイムとサブタイプ

例を見ていく前にライフタイムとサブタイプについて少し。

Rustのライフタイムにおいてライフタイム`'b`がライフタイム`'a`の全期間を含んでいる場合に`'b`は`'a`のサブタイプとなる(`'b` <: `'a`)。

Rustにおいて`'static`は全期間にわたるライフタイムとなるため、`'static`はすべてのライフタイムのサブタイプとみなされる。(任意のライフタイム`'a`に対して`'static` <: `'a`)

以降の例ではコードを簡潔にするためライフタイムのサブタイプとして`'static`を多用していくがあくまでサブタイプの例にすぎない。

## 共変(covariant)

共変であるばあいサブタイプが許容される。
例としては、以下のコードは問題なくコンパイルが通る。

```rust
fn func<'a>(p: &'a i32, q: &'a i32) {}

fn main() {
    let i = 0;
    let j: &'static i32 = &1;

    func(&i, j);
}
```

`j`のライフタイムは`'static`である。`func()`は二つの引数で同じライフタイム`'a`を要求しているので、`func(&i, j)`の呼び出し時`'a`は雑に考えて`&i`のライフタイム、もしくは`'static`として推論されるはずである。

* `'a`を`'static`であると仮定すると、`&i`のライフタイムは`'static`のサブタイプではないためコンパイルできない
* `'a`を`&i`のライフタイムであると仮定すると、`'static'`は`i`のライフタイムのサブタイプ(`'static` <: `&i`のライフタイム)であるためコンパイルできる

よって`'a`は`i`のライフタイムとして推論される。

## 反変(contravariant)

**次の例はコンパイルエラーとなる。**

```rust
fn func<'a>(a: &'a i32, f: fn(&'a i32)) {
}

fn main() {
    let i = 0;

    func(&i, |_: &'static i32| {}); // ERROR
}
```

表にあるとおり`f(T)`の`T`は反変の位置にあり、その影響はライフタイムにも及ぶ。そのため上記コードでは`func(&i, |_: &'static i32| {})`に対して

* `'a`を`&i`のライフタイムと推論すると反変位置に`'static`があるため許容されない
* `'a`を`'static`と推論すると共変位置の`&i`のライフタイムがサブタイプではないため許容されない

となってコンパイルエラーとなる。

### コンパイルできる例

```rust
fn func<'a, F: FnMut(&'a i32)>(a: &'a i32, f: F) {
}


fn main() {

    let i = 1;
    let ref_i = &i;

    {
        let mut v = Vec::new();
        let j = 2;
        v.push(&j);
        func(ref_i, |x: &i32| {
            v.push(&x);
        });
    } // P1

    println!("{}", *ref_i);
}
```

このコードはコンパイルが通る。`func`の2番目の引数のライフタイムは`v`の中身のライフタイムと同じと推論されるはずなのでP1までである。
`ref_i`のライフタイムは明らかにそれより長く反変の関係が成立している。

では次の場合はどうだろうか。

以下のコードはコンパイルが通るが、実際の動きを考えると不思議だ。
`func()`が何もしないため、`func()`から抜けたときに`&i`のライフタイムは終わってしまう。そうするとP1までのライフタイム`'a`に対して共変関係を満たせずコンパイルエラーとなってしまうのではないか。

```rust
fn func<'a, F: FnMut(&'a i32)>(a: &'a i32, f: F) {
}


fn main() {

    let i = 1;

    {
        let mut v = Vec::new();
        let j = 2;
        v.push(&j);
        func(&i, |x: &i32| {
            v.push(&x);
        });
        println!("{:?}", v);
    } // P1
}
```

だがこのコードはコンパイルエラーにはならない。
ライフタイムは推論時、他と競合しない限り必要な分だけ伸張されるからだ。今の場合P1までの`'a`と共変関係を満たすように`&i`のライフタイムが伸張されることになり、結局`&i`のライフタイムもP1まで伸びることとなる。

これは面白い結果である。`func()`の実装が

```rust
fn func<'a, F: FnMut(&'a i32)>(a: &'a i32, mut f: F) {
  f(a);
}
```

こうだった場合、`&i`は`v`に格納されるためライフタイムはP1まで続くことが必須となるがRustのコンパイラはvariantのルールからそのことを推論しているわけである。

## 不変(invariant)

`&mut T`の`T`は不変になるが、`&mut Vec<&'a i32>`のような場合`'a`が不変となる。

```rust
fn func<'a>(x: &mut Vec<&'a i32>, y: &mut Vec<&'a i32>) {
}
```

この関数に対して次のコードはコンパイルが通らない。

```rust
fn main() {
    let mut v1 = Vec::new();
    let i = 1;
    v1.push(&i);

    {
        let mut v2 = Vec::new();
        let j = 1;
        v2.push(&j);
        func(&mut v1, &mut v2);
    }

    println!("{:?}", v1);
}
```

`func()`の引数では`'a`が両方不変位置のため、`'a`は`v1`,`V2`のどちらかのライフタイムと推論されるしか無いが、

* `'a`を`v1`のライフタイムと推論すると、`y: &mut Vec<'a i32>`のライフタイムが`v2`のライフタイムと整合しない
* `'a`を`v2`のライフタイムと推論すると、`x: &mut Vec<'a i32>`のライフタイムが`v1`のライフタイムと整合しない

となるためである。

ちなみに`println!()`行がない場合はコンパイルが通ってしまうが、これは`V1`,`v2`のライフタイムが`func()`の呼び出しで同時に終わるためである。

# 冒頭の例について考える

最後に冒頭の例について考えてみる。

```rust
struct Hoge<'a> {
    i: &'a usize
}

fn func<'a>(_v: &'a mut Hoge<'a>) {
    // noop
}

fn main() {
    let i = 1;
    let mut hoge = Hoge{i: &i}; // P1

    func(&mut hoge); // OK         P2
    func(&mut hoge); // ERROR      P3
}
```

一見P2で作られた`hoge`の可変参照はその場でライフタイムが終わるため、P3の呼び出しも問題ないように思えるが、コンパイルしてみるとP2の可変参照がP3でも生きているかのようなエラーとなる。

今まで見てきた、ライフタイムとVarianceの考え方からこれは以下のように説明できる。

1. `func()`引数の2番めの`'a`は不変位置にあるため、`'a`は`&i`のライフタイムと同一と推論される
2. 引数1番目の`'a`は共変位置にあるため、`hoge`参照のライフタイムは`&i`のライフタイムのサブタイプである必要がある
2. そのため、P2で作られた`hoge`の参照のライフタイムはP3に及ぶこととなり、コンパイルエラーとなる

## 余談

余程のことがない限り`&'a mut Hoge<'a>`のように、参照のライフタイムを型のライフタイムパラメータに使いまわす（？）ような書き方はしないと思う。アンチパターンと言ってもよいのではないだろうか。

# おわりに

Rustにおいてはライフタイムがサブタイプの関係を持つためVarianceを考える機会に頻繁に遭遇するが、そこを解説した文書をあまり見かけず苦労したため自分の手で記述してみた。Rustコード読み書きの一助となれば幸いである。