简体   繁体   中英

impl Default sugar for large struct without derive

If I have a struct I need Default implemented on, if all the field's types have Default implemented themsevles, then I can use the derive macro, otherwise I need to implement Default manually. However, there are some situations where I have a large struct where almost all the fields would be derivable, except there is a small number of fields with types which do not have Default implemented, and I can impl myself because the type is external. This ends up with the situation in the below example. I would prefer to avoid this as it means I need to keep the fields in sync between two places, which is more time consuming, error prone, and verbose. I'm hoping there might be some syntactical suagr to avoid this, but I'm new to rust and can't think of any. I'm thinking something like:

impl Default for Data2 {
    fn default() -> Self {
        Self {
            external_data: ExternalType::One,
            ..Default::default(),
        }
    }
}

Example

#[derive(Default)]
struct Data {
    name: Option<String>,
    flag: bool,
    selected: Vec<String>,
    show_reference: Option<String>,
    index: Option<usize>,
    create_name: String,
    create_type: String,
}

enum ExternalType {
    One,
    Two,
}
// #[derive(Default)]
struct Data2 {
    name: Option<String>,
    flag: bool,
    selected: Vec<String>,
    show_reference: Option<String>,
    index: Option<usize>,
    create_name: String,
    create_type: String,
    external_data: ExternalType,
}
impl Default for Data2 {
    fn default() -> Self {
        Self {
            name: Default::default(),
            flag: Default::default(),
            selected: Default::default(),
            show_reference: Default::default(),
            index: Default::default(),
            create_name: Default::default(),
            create_type: Default::default(),
            external_data: ExternalType::One,
        }
    }
}

I think the smart-default crate can handle this. It lets you derive SmartDefault , which derives Default for all fields except those with a #[default(my_default_value)] attribute.

Example:

use smart_default::SmartDefault;

#[derive(SmartDefault)]
enum Foo {
    Bar,
    #[default]
    Baz {
        #[default = 12]
        a: i32,
        b: i32,
        #[default(Some(Default::default()))]
        c: Option<i32>,
        #[default(_code = "vec![1, 2, 3]")]
        d: Vec<u32>,
        #[default = "four"]
        e: String,
    },
    Qux(i32),
}

There's also the more general derivative crate which lets you customize how all derives work, not just Default , but is a tad more verbose (because it's more general).

Example:

use derivative::Derivative;

#[derive(Derivative)]
#[derivative(Default)]
pub struct RegexOptions {
    pub pats: Vec<String>,
    #[derivative(Default(value="10 * (1 << 20)"))]
    pub size_limit: usize,
    #[derivative(Default(value="2 * (1 << 20)"))]
    pub dfa_size_limit: usize,
    pub case_insensitive: bool,
    pub multi_line: bool,
    pub dot_matches_new_line: bool,
    pub swap_greed: bool,
    pub ignore_whitespace: bool,
    #[derivative(Default(value="true"))]
    pub unicode: bool,
}

As with most Orphan rule problems the only option available is the New Type pattern but I'm not sure this is really an improvement over a manual default impl.

struct ExternalWithDefault(ExternalType);

impl Default for ExternalWithDefault {
  fn default() -> Self {
    Self(ExternalType::One)
  }
}

...

#[derive(Default)]
struct Data2 {
    name: Option<String>,
    flag: bool,
    selected: Vec<String>,
    show_reference: Option<String>,
    index: Option<usize>,
    create_name: String,
    create_type: String,
    external_data: ExternalWithDefault,
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM