Rustで実体とその参照を同居させた構造体を作る方法
解決したいこと
Rustで実体とその参照を同居させた構造体を作りたい。
例)
文字列パーサを作成し、出力結果を構造体で引き回そうとしていますが、所有権で躓きました。unsafeをうまく使う必要があると思いますが具体策がわかりません。
解決方法を教えて下さい。
最小コード
pub struct Parsed<'a> {
src: String,
a: &'a str,
}
impl<'a> Parsed<'a> {
fn new(src: String) -> Self {
let a = &src;
Self { src, a }
}
}
最小例では単に"a = &src"としていますが、実際にはsrcを参照するパース結果を多数の部分文字列に格納しています。
発生している問題・エラー
| impl Parsed {
| -- lifetime `'a` defined here
28 | fn new(src: String) -> Self {
29 | let a = &src;
| ---- borrow of `src` occurs here
30 | Self { src, a }
| -------^^^-----
| | |
| | move out of `src` occurs here
| returning this value requires that `src` is borrowed for `'a`
srcを借用している状態で構造体にmoveしているよ、と言われます。いわれていることは当然ですが、実際には構造体に格納し、後は変更可能にしていれば参照が壊れないことは保証可能です。
自分で試したこと
mem::transmuteまで使いましたが根本的なところでコンパイラを納得させることが出来ません。
1. 借用されていることを無かったことにする。
2. aのライフタイムを適切に設定する
3. その他
適切な宣言とunsafeを使うはずですが知識が足りず。
ご教授願います。
解決コード
解析結果を'staticにして、更にmem::transmuteを行うことで、src借用を解除することが出来ました。
fn main() {
let src = "Hello, world!".to_owned();
let parse = Parsed::new(src);
println!("parsed");
let thread = std::thread::spawn(move || {
println!("thread start.");
println!(" words len={}", parse.args.words().len());
for w in parse.args.words() {
println!(" word: {}", w);
}
println!("thread fin.");
});
thread.join().unwrap();
println!("fin.");
}
pub struct Parsed {
_src: String,
pub args: ParseArgs<'static>,
}
impl<'a> Drop for Parsed {
fn drop(&mut self) {
println!(" Parsed::drop()");
}
}
impl Parsed {
pub fn new(src: String) -> Self {
let args = ParseArgs::parse(&src);
let args = unsafe { std::mem::transmute(args) };
Self {
_src: src,
args: args,
}
}
}
pub struct ParseArgs<'a> {
src: &'a str,
words: Vec<&'a str>,
}
impl<'a> Drop for ParseArgs<'a> {
fn drop(&mut self) {
println!(" ParseArgs::drop()");
}
}
impl<'a> ParseArgs<'a> {
pub fn parse(src: &'a str) -> Self {
let words = src.split(',').collect();
Self {
src: src,
words: words,
}
}
pub fn src(&self) -> &str {
self.src
}
pub fn words(&self) -> &Vec<&str> {
&self.words
}
}
実行結果
parsed
thread start.
words len=2
word: Hello
word: world!
thread fin.
Parsed::drop()
ParseArgs::drop()
fin.
ライフタイム検証
ライフタイムを 'static に指定したとき、dropが消えてしまったらまずいなあと思って一応dropにログ出力してみました。ParseArgs::drop() が表示されていますので、普通に所有権消滅のタイミングで消え去ってくれるようですね。