如果尚未设置,如何设置嵌套在多个选项中的值?

How do I set values nested within multiple Options if they are not already set?

我有这样的配置设置:

#[derive(Debug, Deserialize, Serialize)]
struct Config {
    defaults: Option<Default>,
}

#[derive(Debug, Deserialize, Serialize)]
struct Default {
    duration: Option<Millis>,
}

#[derive(Serialize, Deserialize, Debug)]
struct Millis(u64);

具有 let cfg: &mut Config 的值,如何轻松设置此值的持续时间?

我试过了,如果值不是以以下开头,它会出现恐慌:

*cfg.default.as_mut().unwrap().duration.as_mut().unwrap() = Millis(1234)

除此之外,我还没有找到绕过这些 unwrap 来按需创建值的方法,这更冗长...

if cfg.defaults.is_none() {
    cfg.defaults = Some(Default { duration: None });
}

if cfg.defaults.as_mut().unwrap().duration.is_none() {
    cfg.defaults.as_mut().unwrap().duration = Some(Millis(1234));
}

"The Way"要做什么?

我认为最好的方法是使用模式匹配:

if let Some(Config { defaults: Some(Default { duration: Some(ref mut millis) }) }) = cfg {
    *millis = 1234;
}

或者,或者,嵌套 if lets:

if let Some(Config { ref mut defaults }) = cfg {
    if let Some(Default { ref mut duration }) = *defaults {
        *duration = Some(Millis(1234))
    }
}

但是,这不会按需创造价值。据我所知,没有简单的 boilerplate-less 方法可以做到这一点。其中一种方法是定义您自己的访问器方法,该方法将在访问时处理默认实例化:

impl Config {
    fn defaults(&mut self) -> &mut Default {
        if let Some(ref mut defaults) = *self.defaults {
            defaults
        } else {
            self.defaults = Some(Defaults::new());  // assuming that Defaults::new() exists
            self.defaults.as_mut().unwrap()
        }
    }
}

如果你对每个结构的每个字段都有这样的方法,你将能够做到这一点:

*cfg.defaults().duration() = 1234;

这就是 get_or_insert 方法的用途:

#[derive(Debug)]
struct Config {
    defaults: Option<Default>,
}

#[derive(Debug)]
struct Default {
    duration: Option<Millis>,
}

#[derive(Debug)]
struct Millis(u64);

fn main() {
    let mut config = Config { defaults: None };

    config
        .defaults
        .get_or_insert(Default { duration: None })
        .duration
        .get_or_insert(Millis(0))
        .0 = 42;

    // Config { defaults: Some(Default { duration: Some(Millis(42)) }) }
    println!("{:?}", config);
}

(link to playground)