I have a struct that has 20 fields:
struct StructA {
value1: i32,
value2: i32,
// ...
value19: i32,
day: chrono::NaiveDate,
}
I'd like to impl Default
trait for StructA
. I tried to add #[derive(Default)]
to the struct, but chrono::NaiveDate
doesn't implement Default
.
I then tried to implement Default
for StructA
:
impl Default for StructA {
fn default() -> Self {
Self {
value1: Default::default(),
value2: Default::default(),
// ...
value19: Default::default(),
day: chrono::NaiveDate::from_ymd(2021, 1, 1),
}
}
}
This code works fine, but the parts of value1
through value19
are redundant. Is there a solution with less code?
StructA
to deserialize JSON data via serde-json so I can't change the struct's definition.day: chrono::NaiveDate
is always given from JSON data, so I want to avoid day: Option<chrono::NaiveDate>
.The derivative crate makes this kind of thing a breeze:
#[derive(Derivative)]
#[derivative(Default)]
struct StructA {
value1: i32,
value2: i32,
// ...
value19: i32,
#[derivative(Default(value = "NaiveDate::from_ymd(2021, 1, 1)"))]
day: NaiveDate,
}
If you want to avoid external crates, your options are:
Default::default()
for each numeric field, a simple 0
will work as well.day
an Option
and derive Default
, with the downside that it will default to None
, bear a run-time cost, and you'll have to unwrap()
to access it.day
a newtype that wraps NaiveDate
and implements Default
to set it to the desired value, with the downside that you'll need to access the NaiveDate
through a (zero-cost) field or method.That's a rather dirty trick, but you can wrap your date in an Option
, and it has an implementation of Default
. Then you won't need to implement Default
on your own, you can derive it. To keep the same semantics of StructA::default()
you'll need to write your own method (luckily Rust allows to define default()
method besides already derived Default::default()
) Playground
use chrono;
#[derive(Debug, Default)]
struct StructA {
value1: i32,
value2: i32,
value19: i32,
day: Option<chrono::NaiveDate>,
}
impl StructA {
fn default() -> Self {
let mut instance: Self = Default::default();
instance.day = Some(chrono::NaiveDate::from_ymd(2021, 1, 1));
instance
}
}
fn main() {
println!("{:?}", StructA::default());
// StructA { value1: 0, value2: 0, value19: 0, day: Some(2021-01-01) }
}
Downsides of this version:
.unwrap()
the date everywhere it's useddefault
, but one is Self::default
which fills the date as I implemented and the other is Default::default
which fills the date with None
, you'll need to be careful which you call (calling StructA::default()
invokes Self::default()
)EDIT. Please be careful with this answer (details in the comments by @user4815162342)
In short - the last downside of having two different .default()
methods in one type is dangerous in generic methods with T: Default
arguments, because in this case will be called Default::default()
, which initializes the day
field to None
. The worst part of this effect, is that compiler won't ever warn you about it, thus forcing you to spend your time debugging in case of a bug.
There's one similar approach suggested by @ÖmerErden, where you can again wrap the date into another type, to which you implement Default
on your own. This will ensure that your field will always be initialized, but still forces you to somehow "unwrap" the value. In case of wrapping NaiveDate
into a tuple struct, you can unwrap as simply as instance.day.0
or implement Deref
to this wrapper and unwrap with *instance.day
use chrono;
use std::ops::Deref;
#[derive(Debug)]
struct NaiveDateWrapper(chrono::NaiveDate);
impl Default for NaiveDateWrapper {
fn default() -> Self {
Self(chrono::NaiveDate::from_ymd(2021, 1, 1))
}
}
impl Deref for NaiveDateWrapper {
type Target = chrono::NaiveDate;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Debug, Default)]
struct StructA {
value1: i32,
value2: i32,
value19: i32,
day: NaiveDateWrapper,
}
fn main() {
let v = StructA::default();
println!("{:?}", v.day.0);
println!("{:?}", *v.day);
}
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