更新具有私有字段的Rust结构的公共字段

emk*_*emk 7 struct rust

我有一个Foo表示外部序列化格式的结构.Foo有几十个领域,而且还有更多的领域.令人高兴的是,所有新领域都保证有合理的默认值.

Rust有一个很好的语法,可以使用默认值创建结构,然后更新一些选定的值:

Foo {
  bar: true,
  ..Default::default()
} 
Run Code Online (Sandbox Code Playgroud)

类似地,我们可以使用类型的私有字段来表示"此结构在未来版本中可能包含更多字段"的概念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()
    };
}
Run Code Online (Sandbox Code Playgroud)

这给了我们错误:

error: field `phantom` of struct `F::Foo` is private [--explain E0451]
  --> <anon>:23:5
   |>
23 |>     F::Foo {
   |>     ^
Run Code Online (Sandbox Code Playgroud)

从逻辑上讲,我认为这应该有效,因为我们只是更新公共字段,这将是有用的习惯用法.另一种方法是支持以下内容:

Foo::new()
  .set_bar(true)
Run Code Online (Sandbox Code Playgroud)

......几十个领域都会变得单调乏味.

我该如何解决这个问题?

Chr*_*son 7

默认字段语法不起作用,因为您仍在创建新实例(即使您尝试从另一个对象获取某些字段值).

另一种方法是支持以下内容:

Foo::new()
  .set_bar(true)
Run Code Online (Sandbox Code Playgroud)

......几十个领域都会变得单调乏味.

我不确定即使有很多领域,这个:

Foo::new()
   .set_bar(true)
   .set_foo(17)
   .set_splat("Boing")
Run Code Online (Sandbox Code Playgroud)

比以下更加繁琐:

Foo {
   bar: true,
   foo: 17,
   splat: "Boing",
   ..Foo::default()
}
Run Code Online (Sandbox Code Playgroud)

或者,您可以将公共字段分离为自己的类型:

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,
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你将它称为:

Foo::new(FooPub{ bar: true })
Run Code Online (Sandbox Code Playgroud)

或添加一个fn FooPub::default()以允许您默认某些字段:

Foo::new(FooPub{ bar: true, ..FooPub::default()})
Run Code Online (Sandbox Code Playgroud)


mca*_*ton 5

重命名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()
    };
}
Run Code Online (Sandbox Code Playgroud)

这是一个不那么罕见的模式,现场例子:std::io::ErrorKind::__Nonexhaustive.

当然,如果他们选择使用某个__named领域,用户将不会有任何警告或任何东西,但是这__意味着非常明确.如果需要警告,#[deprecated]可以使用.