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_ref
とrental
がある。
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>
,&T
はStableAddress
を実装している。 (アロケートされたポインタは不変のため)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つの安全性の基礎となる特徴づけを提供する。