この記事はRust2 Advent Calendar 2020の4日目です。
目的
Rustで、実体(String)と、その参照(&str)を同居させた構造体を作りたいのです。目的のためにはunsafeも厭わないのです。割とハマると思うので残すのです。
結論
pub struct Parsed {
_src: String, // 1.初期化後は変更されない
a: &'static str, // 2.ライフタイム追跡対象外
}
impl Parsed{
/// 物凄く重い処理が走るnew関数(だと思って。。。)
pub fn new(src: String) -> Self {
let a = src.as_str(); // 3.src借用
let a = unsafe { // 4.unsafe
std::mem::transmute(a) // 5.型変換&借用外し
};
Self {
_src: src,
a: a,
}
}
pub fn a(&self) -> &str {
self.a
}
}
所有文字列への参照を構造体に保持する
ある文字列をnew関数内で解析して、その結果文字列a:&str
を構造体に保持することを考えます。
さて、a
はsrc
を参照しているわけですから、a
だけを単独で取り出すわけにはいきません。そこで、Parsed
構造体には目的のa
とともに、_src
として元文字列を所有させ、ライフタイムを一致させようとしました。
構造体作成後に構造体の変更を禁止すれば、a
を参照することに問題は無いはずです。
問題:借用チェッカー激おこ
C++なら何も悩まずそのまま実装出来ますが、Rustではunsafeを使わずに実現することは不可能です。new
関数ですが、unsafe部分を外すとこう書けます。
pub fn new(src: String) -> Self {
let a = src.as_str();
Self {
_src: src,
a: a,
}
}
しかし……
error[E0505]: cannot move out of `src` because it is borrowed
--> src/lib.rs:11:19
|
9 | let a = src.as_str(); // 3.src借用
| ------------
| |
| borrow of `src` occurs here
| argument requires that `src` is borrowed for `'static`
10 | Self {
11 | _src: src,
| ^^^ move out of `src` occurs here
src
はa
に借用されています。一方で最後に構造体を作ろうとして'src'をSelfにmoveしようとしますが、これは借用が残っているため認められません。
実際には
'static
関連でもう一つエラーが出ます。
つまり、普通の手段で自己参照構造体を作ることはできません。
解決:unsafeで借用外し
そこでunsafe
の出番です。
pub fn new(src: String) -> Self {
let a = src.as_str(); // 3.src借用
let a = unsafe { // 4.unsafe
std::mem::transmute(a) // 5.型変換&借用外し
};
Self {
_src: src,
a: a,
}
}
}
std::mem::transmute(a)
により、「srcの借用を外す」「Self::aの型(&'static str)に合わせる」の2つを実現しています。_src
は外部からアクセスされないため変更されません。これでParsed::a()
へのアクセスが壊れないことを保証できました!
考察
'static
って、dropは大丈夫? ⇒大丈夫
'static
はプログラムが終了するまで存在するライフタイムと説明されています。そうすると'static
宣言した部分はdropされないのか?などと不安になり挙動を調べました。結果は大丈夫。'static
宣言があろうが、親構造体に含まれる子であることには変わりないため、親のdropに連動して子要素はdropされました。
もうちょいスマートな方法は? ⇒知りたい……
unsafeは外せないでしょうが、std::mem::transmute
はヤバすぎです。もうチョイスマートな型変換が出来そうな気がします。もしくは何とかするcrateがありそうな気がします。
またはそのうちRustが進化して、この手の自己参照を良い感じに解釈してくれるようになることを期待します。
謝辞
Q&A「Rustで実体とその参照を同居させた構造体を作る方法」にて親身になって回答してくださった方々に感謝します。