2016-09-01 32 views
7

私は外部シリアル化フォーマットを表す構造体Fooを持っています。 Fooには数十のフィールドがあり、それ以上は常に追加されます。幸いなことに、すべての新しいフィールドには分かりやすいデフォルト値が保証されています。プライベートフィールドを持つRust構造体のパブリックフィールドを更新する

錆がデフォルト値を使用して構造体を作成し、いくつかの選択された値の更新のための素敵な構文があります。同様に

Foo { 
    bar: true, 
    ..Default::default() 
} 

を、私たちは、この構造体」のアイデアを表現することができ、将来のバージョンで複数のフィールドを有することができます"タイプPhantomDataのプライベートフィールドを使用しています。

しかし、我々はこれら二つのイディオムを組み合わせた場合、我々はエラーを取得:

use std::default::Default; 

mod F { 
    use std::default::Default; 
    use std::marker::PhantomData; 

    pub struct Foo { 
     pub bar: bool, 
     phantom: PhantomData<()>, 
    } 

    impl Default for Foo { 
     fn default() -> Foo { 
      Foo { 
       bar: false, 
       phantom: PhantomData, 
      } 
     } 
    } 
} 

fn main() { 
    F::Foo { 
     bar: true, 
     ..Default::default() 
    }; 
} 

は、これは私たちにエラーを与える:私たちは「ので

論理的
error: field `phantom` of struct `F::Foo` is private [--explain E0451] 
    --> <anon>:23:5 
    |> 
23 |>  F::Foo { 
    |> ^

、私は、これは動作するはずと主張パブリックフィールドを更新するだけで、有用なイディオムになります。代替は、次のようなものをサポートすることです:

Foo::new() 
    .set_bar(true) 

...何十ものフィールドで面倒になるでしょう。

この問題を回避するにはどうすればよいですか?

+2

'phantom'の名前を' __phantom'に変更し、公開し、 '#[doc(hidden)]'にします。実際の例:['std :: io :: ErrorKind :: __ Nonexhaustive'](https://doc.rust-lang.org/src/std/up/src/libstd/io/error.rs.html#162- 168) – mcarton

+2

@mcarton 'std'は' __NoneXhaustive'変種が不安定であると宣言することができる点で少し特殊です。安定したRustを使用している場合、呼び出し元は文字通り使用できません。この同じトリック(私が間違いなく行うことです)を採用している図書館の作家は、慣例に頼らなければなりません。 (私は実際には本当の問題ではないと思いますが、私はちょうどペダントです。) – BurntSushi5

+0

@emk私は** PhantomDataについて学んでいました**。この便利な例をありがとう! – ljedrz

答えて

4

__phantomphantomの名前を変更し、それは公共と#[doc(hidden)]します。

use std::default::Default; 

mod foo { 
    use std::default::Default; 
    use std::marker::PhantomData; 

    pub struct Foo { 
     pub bar: bool, 

     // We make this public but hide it from the docs, making 
     // it private by convention. If you use this, your 
     // program may break even when semver otherwise says it 
     // shouldn't. 
     #[doc(hidden)] 
     pub _phantom: PhantomData<()>, 
    } 

    impl Default for Foo { 
     fn default() -> Foo { 
      Foo { 
       bar: false, 
       _phantom: PhantomData, 
      } 
     } 
    } 
} 

fn main() { 
    foo::Foo { 
     bar: true, 
     ..Default::default() 
    }; 
} 

これはあまり一般的ではないパターンです。ライブの例はstd::io::ErrorKind::__Nonexhaustiveです。

とにかく__namedフィールドを使用することを選択した場合、ユーザーは警告や何も表示されませんが、__は意図を明確にします。警告が必要な場合は、#[deprecated]を使用できます。

+0

ありがとう! @Chris Emersonの答えは、より高いレベルのAPIのための良い選択肢を提供しています。 – emk

6

フィールドの値の一部を別のオブジェクトから取得しようとしても、新しいインスタンスを作成しているため、デフォルトのフィールド構文は機能しません。また

Foo { 
    bar: true, 
    foo: 17, 
    splat: "Boing", 
    ..Foo::default() 
} 

、あなたにpublicフィールドを分離することができます:

Foo::new() 
    .set_bar(true) 
    .set_foo(17) 
    .set_splat("Boing") 

よりもはるかに面倒です:

The alternative is to support something like:

Foo::new() 
    .set_bar(true) 

...which will get tedious with dozens of fields.

私も多くの分野で、これがあることはよく分かりません自分のタイプ:

pub struct FooPub { 
    pub bar: bool, 
    // other pub fields 
} 

pub struct Foo { 
    pub bar: bool, 
    // other pub fields 
    // alternatively, group them: pub public: FooPub, 

    foo: u64, 
} 

impl Foo { 
    pub fn new(init: FooPub) { 
     Foo { 
      bar: init.bar, 
      // other pub fields 
      // alternative: public: init 

      // private fields 
      foo: 17u64, 
     } 
    } 
} 

あなたはその後、としてそれを呼びたい:

Foo::new(FooPub{ bar: true }) 

たり、フィールドの一部をデフォルトせるfn FooPub::default()を追加します。

Foo::new(FooPub{ bar: true, ..FooPub::default()}) 
+0

ありがとうございました!これらは、比較的少数のフィールドを持つ高水準APIのすべての素敵なソリューションです。しかし、私の場合、私はserdeで非直列化されたサードパーティのファイル形式を表現する、より低レベルの構造体を扱っています。私は、まれに使用されている数十のフィールドで動作するものが必要です。構造体を2つに分割する。しかし、あなたの答えは、より単純な問題を持つ人々に役立ちます。ありがとうございました! – emk

関連する問題