【问题标题】:Is there a short way to implement Default for a struct that has a field that does not implement Default?对于具有未实现默认值的字段的结构,是否有一种简短的方法来实现默认值?
【发布时间】:2021-09-21 13:11:10
【问题描述】:

我有一个包含 20 个字段的结构:

struct StructA {
    value1: i32,
    value2: i32,
    // ...
    value19: i32,
    day: chrono::NaiveDate,
}

我想为StructA 实现Default 特征。我尝试将#[derive(Default)] 添加到结构中,但chrono::NaiveDate 没有实现Default

然后我尝试为StructA 实现Default

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),
        }
    }
}

这段代码运行良好,但value1value19 的部分是多余的。有没有代码更少的解决方案?

  • 我定义了 StructA 以通过 serde-json 反序列化 JSON 数据,因此我无法更改结构的定义。
  • day: chrono::NaiveDate 的值始终来自 JSON 数据,因此我想避免使用 day: Option<chrono::NaiveDate>

【问题讨论】:

  • 我更喜欢管理较小的结构,也可以应用内部模式:play.rust-lang.org/…
  • @ÖmerErden 是的。您的评论是更好的方法。但现在,我定义了用于序列化/反序列化现有 JSON 数据的 StructA。所以我不能改变 StructA 的结构。
  • 您始终可以实现自定义序列化器/反序列化器来以不同的结构表示您的数据,实际上如果您使用 serde #[serde(flatten)] 可以轻松解决内部模式的问题。
  • 我不知道#[serde(flatten)]。谢谢。

标签: rust


【解决方案1】:

derivative crate 让这种事情变得轻而易举:

#[derive(Derivative)]
#[derivative(Default)]
struct StructA {
    value1: i32,
    value2: i32,
    // ...
    value19: i32,
    #[derivative(Default(value = "NaiveDate::from_ymd(2021, 1, 1)"))]
    day: NaiveDate,
}

如果您想避免使用外部 crate,您的选择是:

  • 您已经使用的方法,缺点是您必须命名所有字段。另请注意,您无需为每个数字字段重复 Default::default(),简单的 0 也可以。
  • day 设为Option 并派生Default,缺点是它将默认为None,承担运行时成本,并且您必须unwrap() 才能访问它。
  • 使day 包装NaiveDate 并实现Default 以将其设置为所需值的新类型,缺点是您需要通过(零成本)字段访问NaiveDate 或方法。

【讨论】:

    【解决方案2】:

    这是一个相当肮脏的技巧,但您可以将您的日期包装在Option 中,并且它具有Default 的实现。那么你就不需要自己实现Default了,你可以推导出来。要保持 StructA::default() 的相同语义,您需要编写自己的方法(幸运的是,除了已经派生的 Default::default() 之外,Rust 还允许定义 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) }
    }
    

    此版本的缺点:

    • 需要.unwrap()使用日期
    • 两个方法同名default,但一个是Self::default,它填充我实现的日期,另一个是Default::default,它用None填充日期,你需要小心你调用(调用StructA::default() 调用Self::default()

    编辑。请注意这个答案(@user4815162342 在 cmets 中的详细信息)

    简而言之 - 在一个类型中拥有两个不同的 .default() 方法的最后一个缺点在带有 T: Default 参数的泛型方法中是危险的,因为在这种情况下将调用 Default::default(),它将 day 字段初始化为None。这种效果最糟糕的部分是编译器永远不会警告您,从而迫使您花时间调试以防出现错误。

    @ÖmerErden 建议了一种类似的方法,您可以再次将日期包装成另一种类型,您可以自行实现 Default。这将确保您的字段将始终被初始化,但仍会强制您以某种方式 "unwrap" 值。如果将NaiveDate 包装到一个元组结构中,您可以像instance.day.0 一样简单地解包,或者将Deref 实现到这个包装器并使用*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);
    }
    

    【讨论】:

    • 好主意,也可以使用 Wrapper 结构来摆脱 unwrap : NaiveDateWrapper(chrono::NaiveDate)
    • 当在需要T: Default 的通用代码中使用StructA 时,此技巧将严重失败(并且静默)。它将静默调用Default::default() 并且无法正确初始化结构。
    • @user4815162342 不能同意更多,但可以通过@Ömer Erden 的建议部分解决 - 将日期类型包装在一些元组结构中并为其实现 Default
    • 这个想法可能是仅使用标准库的最优雅的方法。我鼓励您编辑您的答案以使其更加突出。我没有投反对票,因为我有一个“竞争”的答案,但是面对泛型代码,答案中的肮脏技巧解决方案确实很危险,而泛型代码可能是 OP 想要在第一名。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-22
    • 1970-01-01
    • 2013-11-18
    • 1970-01-01
    • 1970-01-01
    • 2019-08-24
    相关资源
    最近更新 更多