【问题标题】:How can I lazy initialize/fill an `Option` with a fallible initializer?如何使用可出错的初始化程序延迟初始化/填充“选项”?
【发布时间】:2021-01-16 16:27:29
【问题描述】:

假设我有一个包含Option<Resource> 的结构,其中Resource 是我需要使用的某种类型,需要分配外部资源(在实际情况下是GPU 内存),因此可能会失败。在一个方法中,如果尚未完成分配,我想尝试分配,但如果失败则传播或处理错误。

如果不是失败案例,Option::get_or_insert_with 将是完美的。事实上,我想到的最整洁的解决方案涉及一个 unwrap(),这是不优雅的,因为它看起来像一个潜在的恐慌:

struct Container {
    resource: Option<Resource>,
    ...
}

impl Container {
    ...
    fn activate(&mut self) -> Result<(), Error> {
        if self.resource.is_none() {
            self.resource = Some(Resource::new()?);
        }
        let resource: &mut Resource = self.resource.as_mut().unwrap();
        // ... now do things with `resource` ...
        Ok(())
    }
    ...
}

有没有比这更轻松地初始化Option 的方法?需要明确的是,我并不是单独寻求避免unwrap(),而是整体可读性。如果替代方案更加复杂和间接,我宁愿坚持下去。


完整的示例代码(on Rust Playground):

#[derive(Debug)]
struct Resource {}
#[derive(Debug)]
struct Error;

impl Resource {
    fn new() -> Result<Self, Error> {
        Ok(Resource {})
    }

    fn write(&mut self) {}
}

#[derive(Debug)]
struct Container {
    resource: Option<Resource>,
}

impl Container {
    fn new() -> Self {
        Self { resource: None }
    }

    fn activate(&mut self) -> Result<(), Error> {
        if self.resource.is_none() {
            self.resource = Some(Resource::new()?);
        }
        self.resource.as_mut().unwrap().write();
        Ok(())
    }
}

fn main() {
    Container::new().activate();
}

【问题讨论】:

    标签: rust optional lazy-initialization


    【解决方案1】:

    确实,get_or_insert_with() 但返回 Result&lt;&amp;mut T, E&gt; 是您可以用来简化代码的方法。但是,正如您已经发现 Option 没有例如try_get_or_insert_with() 方法。

    其他解决方法也类似冗长。但是,您可以使用matchget_or_insert() 来避免unwrap(),如下所示:

    fn activate(&mut self) -> Result<(), Error> {
        let res = match &mut self.resource {
            Some(res) => res,
            None => self.resource.get_or_insert(Resource::new()?),
        };
        res.write();
    
        Ok(())
    }
    

    如果经常使用,那么你也可以定义自己的try_get_or_insert_with() trait 方法,并为Option&lt;T&gt; 实现它。

    trait TryGetOrInsert<T> {
        fn try_get_or_insert_with<E, F>(&mut self, f: F) -> Result<&mut T, E>
        where
            F: FnOnce() -> Result<T, E>;
    }
    
    impl<T> TryGetOrInsert<T> for Option<T> {
        fn try_get_or_insert_with<E, F>(&mut self, f: F) -> Result<&mut T, E>
        where
            F: FnOnce() -> Result<T, E>,
        {
            match self {
                Some(value) => Ok(value),
                None => Ok(self.get_or_insert(f()?)),
            }
        }
    }
    

    那么现在您可以将activate() 方法简化为以下内容:

    fn activate(&mut self) -> Result<(), Error> {
        let res = self.resource.try_get_or_insert_with(|| Resource::new())?;
        res.write();
    
        Ok(())
    }
    

    【讨论】:

    • 我认为在用例出现不止一次之前我不会添加这么多代码,但我喜欢使用self.get_or_insert(value) 处理移动和借用的技巧!跨度>
    • @KevinReid 我更新了答案,使用matchget_or_insert 显示另一个变体
    猜你喜欢
    • 2011-11-17
    • 2020-05-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-23
    • 1970-01-01
    • 1970-01-01
    • 2020-10-08
    相关资源
    最近更新 更多