I have a struct Foo
which represents an external serialization format. Foo
has dozens of fields, and more are added all the time. Happily, all new fields are guaranteed to have sensible default values.
Rust has a nice syntax for creating a struct using default values and then updating a few selected values:
Foo {
bar: true,
..Default::default()
}
Similarly, we can represent the idea of "this struct may have more fields in a future version" using a private field of type PhantomData
.
But if we combine these two idioms, we get an error:
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()
};
}
This gives us the error:
error: field `phantom` of struct `F::Foo` is private [--explain E0451]
--> <anon>:23:5
|>
23 |> F::Foo {
|> ^
Logically, I would argue that this should work, because we're only updating public fields, and it would be useful idiom. The alternative is to support something like:
Foo::new()
.set_bar(true)
...which will get tedious with dozens of fields.
How can I work around this problem?
The default field syntax doesn't work because you're still creating a new instance (even if you're trying to take some of the field values from another object).
The alternative is to support something like:
Foo::new() .set_bar(true)
...which will get tedious with dozens of fields.
I'm not sure that even with many fields, this:
Foo::new()
.set_bar(true)
.set_foo(17)
.set_splat("Boing")
is significantly more tedious than:
Foo {
bar: true,
foo: 17,
splat: "Boing",
..Foo::default()
}
Alternatively, you could separate out the public fields into their own type:
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,
}
}
}
You'd then call it as:
Foo::new(FooPub{ bar: true })
or add a fn FooPub::default()
to let you default some of the fields:
Foo::new(FooPub{ bar: true, ..FooPub::default()})
Rename phantom
to __phantom
, make it public and #[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()
};
}
This is a not so uncommon pattern, live example: std::io::ErrorKind::__Nonexhaustive
.
Sure, users won't have any warning or anything if they choose to use a __named
field anyway, but the __
makes the intent pretty clear. If a warning is required, #[deprecated]
could be used.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With