LoginSignup
14
1

More than 1 year has passed since last update.

erased_serdeで学ぶobject safeじゃないtraitをtrait objectで使う方法

Last updated at Posted at 2021-12-09

この記事はRust Advent Calendar 2021 カレンダー2の10日目の記事です。

trait objectとobject safety

みなさんtrait objectを使っていますか?
もちろんパフォーマンスのことなどを考えると、できるだけenum dispatchなどを使う方がいいのですが、利便性やapiの都合、パフォーマンスがいらない場合などではまあ便利だとおもいます。

あるtraitをtrait objectとして使う場合、traitがobject safetyという規則に従ったものでないといけません。この中に

  • traitのメソッドがジェネリックなパラメータを持ってはいけない
  • traitがSelf:Sizedを要求してはいけない

というのがあります。

trait ObjectSafe1{
    // object safe なメソッド
    fn safe_method_1(&self, value : i32) -> i32;
}

//object safeなメソッドだけなのでobject safeなtrait
trait ObjectSafe2<T>{
    // object safe なメソッド
    fn safe_method_2(&self, t : T) -> T;
}

//object safeでないメソッドがあるのでobject safeでないtrait
trait NotObjectSafe{
    // object safe でないメソッド
    fn not_safe_method_3<T>(&self, t : T) -> T;
    // 実はここにwhere Self : Sizedとつければobject safeにはなるが、
    // dynはSizedではないのでdynした時点でメソッドとして使えなくなる。
}

//sized を要求するのでobject safeでない
trait NotObjectSafe2 : Sized{
    fn method_4(self, value : i32) -> i32;
}

struct S;
impl ObjectSafe1 for S {
    fn safe_method_1(&self, value : i32) -> i32 { /*省略*/ }
}
impl ObjectSafe2<i32> for S {
    fn safe_method_2(&self, t : i32) -> i32 { /*省略*/ }
}
impl NotObjectSafe1 for S {
    fn not_safe_method_3<T>(&self, t : T) -> T  { /*省略*/ }
}
impl NotObjectSafe2 for S {
    fn method_4(self, value : i32) -> i32  { /*省略*/ }
}

fn test(){
    let x : Box<dyn ObjectSafe1> = Box::new(S);
    x.safe_method_1(1);
    let x : Box<dyn ObjectSafe2<i32>> = Box::new(S);
    x.safe_method_2(2);
    //let x : Box<dyn NotObjectSafe1> = Box::new(S); compile error
    // let x : Box<dyn NotObjectSafe2> = Box::new(S);
}

以上のようにNotObjectSafeというトレイトはtrait objectにしようとするとcompile errorとなります。
このような例としてserdeSerializeSerializerがあります。

// serde::Serialize
pub trait Serialize {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer;
}

// serde::Serializer
pub trait Serializer: Sized {
    /*省略*/
}

とうぜんこれもobject safeでないためtrait object にはできません。

erased_serde

そんなserde::Serializeに対してトレイトオブジェクト化できるようにしたのがerased_serdeです。

ではどうやってobject safetyを回避したのでしょうか。

その方法は単純で、object safeなtraitを別に用意し、そのtrait object(の参照)に対して元のtraitをimplしてやることによって達成してます。

まずobject safetyなmethodを定義します。

// erased_serde::Serialize
pub trait Serialize {
    fn erased_serialize(&self, v: &mut dyn Serializer) -> Result<Ok, Error>;
}

// erased_serde::Serializer
pub trait Serializer { /*省略*/ }

そしてそれらのtrait objectにたいして元のtraitをimplします。

//実際にはともに +Send, +Sync, +Send+Syncに対しても定義している 

impl<'a> serde::Serializer for &'a mut dyn erased_serde::Serializer { /*省略*/ }

impl<'erased> serde::Serialize for dyn Serialize + 'erased { /*省略*/ }

//serde::Serialzieをerased_serde::Serialzieでつかえるようにwrapする関数
impl dyn Serializer {
   pub fn erase<S : serde::Serialzie>(serializer: S) -> Serializer<S>
}

これによってtraitを少しwrapしてやることによいってtrait objectとして使えるようにしています。

よって似たようにobject safeでないtraitでも少しの工夫でtrait objectにできます。

参考文献

object safetyの定義
https://github.com/rust-lang/rfcs/blob/master/text/0255-object-safety.md

asyncなiteratorをboxed dynで使う方法についての英語記事、
これのmacroがあったらいいなという提案をしている。
https://smallcultfollowing.com/babysteps//blog/2021/10/15/dyn-async-traits-part-6/

14
1
0

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
14
1