質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
シリアライゼーション

シリアライゼーションとはデータ構造を特定のフォーマットに変換するプロセスのことです。これはデータの保存・送信・もしくは後々の再構築を容易にするために行われます。

データ構造

データ構造とは、データの集まりをコンピュータの中で効果的に扱うために、一定の形式に系統立てて格納する形式を指します。(配列/連想配列/木構造など)

Rust

Rustは、MoFoが支援するプログラミング言語。高速性を維持しつつも、メモリ管理を安全に行うことが可能な言語です。同じコンパイル言語であるC言語やC++では困難だったマルチスレッドを実装しやすく、並行性という点においても優れています。

Q&A

解決済

1回答

3086閲覧

Rustのserdeで&'a [T]をフィールドに持つ構造体をデシリアライズする方法

mask_mus

総合スコア37

シリアライゼーション

シリアライゼーションとはデータ構造を特定のフォーマットに変換するプロセスのことです。これはデータの保存・送信・もしくは後々の再構築を容易にするために行われます。

データ構造

データ構造とは、データの集まりをコンピュータの中で効果的に扱うために、一定の形式に系統立てて格納する形式を指します。(配列/連想配列/木構造など)

Rust

Rustは、MoFoが支援するプログラミング言語。高速性を維持しつつも、メモリ管理を安全に行うことが可能な言語です。同じコンパイル言語であるC言語やC++では困難だったマルチスレッドを実装しやすく、並行性という点においても優れています。

0グッド

1クリップ

投稿2021/05/29 06:44

以下のようなソースコードを書いているのですが、struct Aでエラーが起きてしまいます。
エラー文から対処方法を解読できず、なにを実装すればいいのか悩んでいます。
多少強引な方法でも良いので、解決方法を教えていただきたいです。

Rust

1#[derive(Serialize, Deserialize)] 2pub enum MyEnum{ 3 A, 4 B, 5 C, 6 D, 7 E, 8} 9 10#[derive(Serialize, Deserialize)] 11pub struct B{ 12 pub num: u32, 13 pub level: i32, 14 pub param: MyEnum, 15} 16 17#[derive(Serialize, Deserialize)] 18struct A<'a> { 19 data: &'a [B], // ここでエラー[E0227] 20 index: usize, 21}

error

1[E0277] the trait bound `&'a [B]: Deserialize<'_>` is not satisfied. 2[Note] the trait `Deserialize<'_>` is not implemented for `&'a [B]`

serdeのバージョン:
serde = { version = "^1.0.126", features = ["derive"] }
serde_json = "^1.0.64"

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

方法1

最も正しいと思われる方法は、参照を使うのを諦める方法です。 &str&[u8] などの特別な場合 (zero-copy deserialization) を除き、serdeは参照をデシリアライズできません。

rust

1#[derive(Serialize, Deserialize)] 2struct A { 3 data: Vec<B>, 4 index: usize, 5}

何らかの理由で data: &[B] としなければいけない場合、まずその理由を再検討してください

  • Vecアロケーションコストが気になっていますか?配列のデシリアライズをしたいのであれば、これはほぼ間違いなく必要で妥当なコストです。Rustは不要なアロケーションコストを削ることはできますが、必要なアロケーションコストを削ることはできません。
  • Vecコピーコストが気になっていますか?もし配列が大きく、かつコピーが頻繁に発生しそうなら、 Rc<Vec<B>> または Arc<Vec<B>> として中身のコピーコストを抑えるのがいいでしょう。
  • 後続のAPIが data: &[B] を要求していますか? どうしてもそのAPIを変えられない場合、 data: &[B] を持つ struct A<'a>data: Vec<B> を持つ struct AOwned を用意して、デシリアライズしてから AOwnedA の変換を行うという手があります。このような冗長なコードは、Rustでパフォーマンスを追求するとしばしば起きてしまうので、そういうものだと思って諦めるのも手でしょう。

方法2

参照を使うには、そのデータが既にメモリ上のどこかにそのままの形で存在している必要があります。方法2は入力バイト列中のデータをそのまま使う方法です。この方法は次のような注意点があります。

  • 入力中に構造体のメモリレイアウト通りのデータがある場合だけ使える極めて限定的な方法です。
  • B の中身として、基本的に整数や整数の固定長配列だけを扱えます。
  • 2バイト以上の整数を使う場合、CPUによって違う挙動 (エンディアンの差異) が起きます。
  • 2バイト以上の整数を使う場合、入力データのアドレスがきりのいい位置にある必要があります。 (アラインメント)
  • serdeは色々なフォーマットをサポートしていますが、方法2が使えるのはzero-copy deserializationをサポートしているフォーマットだけです。そのようなフォーマットとしてmsgpack, bincodeなどがあります。(JSONなどでも文字列リテラルのzero-copy deserializationができますが、structにマップするのはまず無理でしょう。)
  • シリアライズとの一貫性は自力で保証する必要があります。

エンディアン要件やアラインメント要件があるので、この方法が有益な場面は非常に限定されています。ELFのオブジェクトファイルのように、それ用に設計された(しかもアーキテクチャごとに固有の)ファイルフォーマットをmmapして使うケースがこの例にあたります。

メモリをstructにマップする処理を自分でやるとほぼ確実に未定義動作を踏むことになるので、bytemuckなど安全性を担保してくれるライブラリを使います。

rust

1use bytemuck::{try_cast_slice, Pod, Zeroable}; 2use serde::{Deserialize, Deserializer, Serialize}; 3 4#[derive(Clone, Copy, Serialize, Zeroable, Pod)] 5#[repr(C)] 6pub struct B { 7 pub num: u32, 8 pub level: i32, 9 pub param: u32, // enumはPodにならないので使えない 10} 11 12#[derive(Serialize, Deserialize)] 13struct A<'a> { 14 // シリアライズとデシリアライズの一貫性は自力で保つ必要がある 15 // (このコード例では省略している) 16 #[serde(borrow, deserialize_with = "deserialize_b_slice")] 17 data: &'a [B], // ここでエラー[E0227] 18 index: usize, 19} 20 21fn deserialize_b_slice<'de, D>(de: D) -> Result<&'de [B], D::Error> 22where 23 D: Deserializer<'de>, 24{ 25 // 実際にはunwrap()せずにD::Errorへの変換も実装するべきだが、今回は省略 26 Ok(try_cast_slice(Deserialize::deserialize(de)?).unwrap()) 27}

方法3

どうしてもDeserializeに参照を吐かせなければいけない謎の理由がある場合、最終手段のひとつとしてメモリリークを許容するという手があります。

  • この方法はメモリリークします。100%確実にメモリリークします。長時間起動するプログラムの初期化以外の部分で使ったら間違いなく問題になります。
  • 結局アロケーションコストはかかるので、 Vec と比べてパフォーマンス上優位とは言えないでしょう。

rust

1use serde::{Deserialize, Deserializer, Serialize}; 2 3#[derive(Serialize, Deserialize)] 4pub enum MyEnum { 5 A, 6 B, 7 C, 8 D, 9 E, 10} 11 12#[derive(Serialize, Deserialize)] 13pub struct B { 14 pub num: u32, 15 pub level: i32, 16 pub param: MyEnum, 17} 18 19#[derive(Serialize, Deserialize)] 20struct A<'a> { 21 #[serde(deserialize_with = "deserialize_data_with_leak")] 22 data: &'a [B], // ここでエラー[E0227] 23 index: usize, 24} 25 26fn deserialize_data_with_leak<'a, 'de, D>(de: D) -> Result<&'a [B], D::Error> 27where 28 D: Deserializer<'de>, 29{ 30 // !!! 100%メモリリーク !!! 31 Ok(Vec::<B>::deserialize(de)?.leak()) 32}

方法4

serdeにはDeserializeSeedという、パラメーターつきのデシリアライズを行うためのトレイトがあります。Arenaをパラメーターに置いてデシリアライズすればArena管理下のメモリを指す参照を出力することができます。

rust

1use serde::de::{DeserializeSeed, SeqAccess, Visitor}; 2use serde::{Deserialize, Deserializer, Serialize}; 3use typed_arena::Arena; 4 5#[derive(Serialize, Deserialize)] 6pub enum MyEnum { 7 A, 8 B, 9 C, 10 D, 11 E, 12} 13 14#[derive(Serialize, Deserialize)] 15pub struct B { 16 pub num: u32, 17 pub level: i32, 18 pub param: MyEnum, 19} 20 21#[derive(Serialize)] 22struct A<'a> { 23 data: &'a [B], 24 index: usize, 25} 26 27struct DeserializeA<'a> { 28 arena: &'a Arena<B>, 29} 30 31impl<'a, 'de> DeserializeSeed<'de> for DeserializeA<'a> { 32 type Value = A<'a>; 33 fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error> 34 where 35 D: Deserializer<'de>, 36 { 37 struct AVisitor<'a> { 38 arena: &'a Arena<B>, 39 } 40 impl<'a, 'de> Visitor<'de> for AVisitor<'a> { 41 type Value = A<'a>; 42 43 // mapなど他のvisitorも実装しないといけない 44 fn visit_seq<SA>(self, mut seq: SA) -> Result<Self::Value, SA::Error> 45 where 46 SA: SeqAccess<'de>, 47 { 48 // 本当はunwrapせずにエラーハンドリングを書く必要がある 49 let data: Vec<_> = seq.next_element()?.unwrap(); 50 let index = seq.next_element()?.unwrap(); 51 52 let data = self.arena.alloc_extend(data.into_iter()); 53 Ok(A { data, index }) 54 } 55 56 fn expecting(&self, _f: &mut std::fmt::Formatter) -> std::fmt::Result { 57 Ok(()) 58 } 59 } 60 deserializer.deserialize_struct("A", &["data", "index"], AVisitor { arena: self.arena }) 61 } 62}

ただしDeserializeSeedのサポートは極めて限定的でserde-jsonすらサポートしていない状態です。また上の例では中間データとして Vec を使っているのでアロケーションコスト的にも有利ではなくメリットが薄いです。(個数が未知の配列をデシリアライズするための中間領域はどうしても必要になる)

またderive macro上もDeserializeと同等の使い勝手は望めないでしょう。こちらもほとんど最終手段だと思います。

投稿2021/05/29 13:22

qnighy

総合スコア210

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問