Vecと参照を同時に返す

概要: Rustでは Vec<T> とその要素への参照を同時に返すことはできないが、これを部分的に可能にするcrateはある。

やりたいこと

以下のように、 Vec<T> とその要素への参照を同時に返したい。(あるいは、こういった組を構造体に格納したい。)

fn foo() -> (Vec<i32>, &[i32]) {
    let v = vec![1, 2, 3];
    let s = &v[1..2];
    (v, s)
}

なぜできないか

Rustのライフタイムの枠組みでは、上のように(もし仮にコンパイルが通った場合に)実際に動作するパターンと、以下のように未定義動作になるパターンを体系的に区別することができない。

fn foo_invalid1() -> (Vec<i32>, &[i32]) {
    let mut v = vec![1, 2, 3];
    let s = &v[1..2];
    v.push(10); // pushによってリアロケーションが発生するとsが壊れる
    (v, s)
}
fn foo_invalid2() -> ([i32; 3], &[i32]) {
    let v = [1, 2, 3];
    let s = &v[1..2]; // vがムーブされるだけでsが壊れる
    (v, s)
}

つまり、親のムーブに対して不変な領域であることと、アロケーションが発生しないことを保証する必要がある。

ライブラリレベルでのサポート

トレイトで上記の条件を保証することで、この需要に部分的に答えるライブラリがある。著名なものとしてowning_refrentalがある。

owning_ref

[dependencies]
owning_ref = "0.3.3"
extern crate owning_ref;

use owning_ref::OwningRef;

fn foo() -> OwningRef<Vec<i32>, [i32]> {
    let v = vec![1, 2, 3];
    OwningRef::new(v).map(|v| {
        &v[1..2]
    })
}

このcrateで中心的な役割を果たしているのはowning_ref::StableAddressである (stable_deref_trait::StableDerefの別名であることに注意)。これは deref/deref_mut が常に同じポインタを返すという契約のもと unsafe になっている。より具体的には、これ自体がムーブされた場合と、 deref/deref_mut 先のオブジェクトに変更が加えられた場合に、ポインタが変化しないことが要求されている。

例えば、

  • Vec<T>, Box<T>, Rc<T>, &TStableAddress を実装している。 (アロケートされたポインタは不変のため)
  • Cell<T>, RefCell<T>StableAddress を実装していない。 (ムーブによりアドレスが変化してしまうため)
  • 標準ライブラリにはないが、 deref_mut がポインタを変更するような型の場合は StableAddress を実装してはいけない。

StableAddress の前提のもと、例えば

  • (Vec<T>, &[T]) と書きたかった部分は OwnedRef<Vec<T>, [T]> と書くことができる。
  • 同様に (Rc<T>, &mut U) と書きたければ OwnedRefMut<Rc<T>, U> と書くことができる。

その他、 StableAddress だけではカバーできないユースケースのために ToHandle, ToHandleMut, CloneStableAddress が用意されている。

  • ToHandle, ToHandleMut を実装している型に対しては OwnedHandle が使用できる。
  • CloneStableAddress を実装している型に対しては OwnedRef がcloneできる。

rental

rental の安全性保証はowning_refと同じく、 StableDeref (StableAddress) に基づいている。こちらはユーザー定義の構造体に所有者と参照を同時に入れるためのマクロを提供する。

#[macro_use]
extern crate rental;

rental! {
    // rental! の直下はmodである必要がある
    pub mod foo_struct {
        // rental! の対象となる構造体には #[rental] か #[rental_mut] をつける
        #[rental]
        pub struct VecAndSlice<T: 'static> { // T: 'static が必要
            v: Vec<T>,
            s: &'v [T], // この 'v というライフタイム名は上の v というフィールド名と一致させる
        }
        // 生成されるメソッド
        // VecAndSlice::new(Vec<T>, f: F)
        //   fにはvをDerefしたものが渡される
        // VecAndSlice::try_new(Vec<T>, f)
        //   fがResultを返すときに使う (TryNewResultが返ってくる)
        // VecAndSlice::try_new_or_drop(Vec<T>, f)
        //   try_newと似ているが、fがErrを返したらVecは解放される。
        // unsafe VecAndSlice::borrow(&self)
        // unsafe VecAndSlice::borrow_mut(&mut self)
        //   危険を承知で、ポインタを直接取得する。
        // VecAndSlice::rent(&self, f: F)
        //   fは参照を受け取る。
        // VecAndSlice::rent_mut(&self, f: F)
        //   fはmutableな参照を受け取る。
        // VecAndSlice::ref_rent(&self, f: F)
        // VecAndSlice::ref_rent_mut(&self, f: F)
        //   rent, rent_mutに似ているが、当該の参照を返したいときに使う。
        // VecAndSlice::maybe_ref_rent(&self, f: F)
        // VecAndSlice::try_ref_rent(&self, f: F)
        // VecAndSlice::maybe_ref_rent_mut(&self, f: F)
        // VecAndSlice::try_ref_rent_mut(&self, f: F)
        //   ref_rent, ref_rent_mutに似ているが、OptionやResultが返される。
        // VecAndSlice::into_head(self)
        //   参照を捨てて、Vecだけを返す。
        //
        // また、 VecAndSliceはDerefとAsRefを実装する。
    }
}

use foo_struct::VecAndSlice;

fn foo() -> VecAndSlice<i32> {
    let v = vec![1, 2, 3];
    VecAndSlice::new(v, |v| {
        &v[1..2]
    })
}

rental!マクロが生成するメソッドの一覧はここには書いていないが、を見るのが早い。

まとめ

Rustでは Vec<T> とその要素への参照を同時に返すことはできないが、これを部分的に可能にするcrateはある。

  • owning_ref は、ヒープへの所有権とその借用を同時に持つための一般的な構造体を提供する。
  • rental は、ヒープへの所有権とその借用を同時に持つ構造体を安全に定義するためのマクロを提供する。
  • stable_deref_trait は、上記2つの安全性の基礎となる特徴づけを提供する。