我怎样才能延迟 initialize/fill 一个带有易错初始化器的 `Option`?
How can I lazy initialize/fill an `Option` with a fallible initializer?
假设我有一个包含 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();
}
确实,get_or_insert_with()
but returning Result<&mut T, E>
is what you could use to simplify your code. However, as you already discovered Option
没有例如try_get_or_insert_with()
方法。
其他解决方法同样冗长。但是,您可以像这样使用 match
和 get_or_insert()
, to avoid the 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<T>
实现它。
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(())
}
假设我有一个包含 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();
}
确实,get_or_insert_with()
but returning Result<&mut T, E>
is what you could use to simplify your code. However, as you already discovered Option
没有例如try_get_or_insert_with()
方法。
其他解决方法同样冗长。但是,您可以像这样使用 match
和 get_or_insert()
, to avoid the 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<T>
实现它。
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(())
}