13
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Rust 2Advent Calendar 2020

Day 4

Rustで自己参照構造体を作る

Last updated at Posted at 2020-12-03

この記事は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を構造体に保持することを考えます。

さて、asrcを参照しているわけですから、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

srcaに借用されています。一方で最後に構造体を作ろうとして'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で実体とその参照を同居させた構造体を作る方法」にて親身になって回答してくださった方々に感謝します。

13
5
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?