2016-09-01 79 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

重命名phantom__phantom,使公衆和#[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

默認字段語法不起作用,因爲您仍在創建新實例(即使您試圖從另一個對象中獲取某些字段值)。

The alternative is to support something like:

Foo::new() 
    .set_bar(true) 

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

我不知道,即使有許多領域,這一點:

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

是顯著更繁瑣的比:

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

或者,你可以在公共領域分離成他們自己的型號:

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'反序列化的第三方文件格式,並且我需要一些可以與幾十個很少使用的字段一起工作的東西,並且不需要我將結構拆分爲兩部分。但是你的回答會幫助那些有更簡單問題的人。謝謝! – emk