我正在尝试实施和应用an optimized replace_with
helper for Option
。我是这样实现的:
trait OptionExt<T> {
#[inline]
fn replace_with<F>(&mut self, f: F)
where
F: FnOnce(Option<T>) -> Option<T>;
}
impl<T> OptionExt<T> for Option<T> {
#[inline]
fn replace_with<F>(&mut self, f: F)
where
F: FnOnce(Option<T>) -> Option<T>,
{
let mut x = f(self.take());
mem::swap(self, &mut x);
debug_assert!(x.is_none());
mem::forget(x);
}
}
对于一个简单的用例,它工作正常。
天真的实现:
let merged = merge(lower_node.right.take(), Some(greater_node));
lower_node.right = merged;
优化的实现(details about the trick):
let mut merged = merge(lower_node.right.take(), Some(greater_node));
mem::swap(&mut lower_node.right, &mut merged);
debug_assert!(merged.is_none());
mem::forget(merged);
具有更好接口的优化实现:
lower_node.right.replace_with(|node| merge(node, Some(greater_node));
当我要实现一个split_binary
函数以返回一对节点时,这种方法不能很好地发挥作用:
天真的实现:
let (left_node, right_node) = split_binary(orig_node.right.take(), value);
orig_node.right = left_node;
(Some(orig_node), right_node)
最佳实现:
let (mut left_node, right_node) = split_binary(orig_node.right.take(), value);
mem::swap(&mut orig_node.right, &mut left_node);
debug_assert!(left_node.is_none());
mem::forget(left_node);
(Some(orig_node), right_node)
具有更好接口的优化实现(由于right_node
现在位于闭包内,因此无法编译):
orig_node.right.replace_with(|node| {
let (left_node, right_node) = split_binary(node, value);
left_node
});
(Some(orig_node), right_node)
我可以通过在闭包之外定义一个辅助器new_right_node
来使其编译:
let mut new_right_node = None;
orig_node.right.replace_with(|node| {
let (left_node, right_node) = split_binary(node, value);
new_right_node = right_node;
left_node
});
(Some(orig_node), new_right_node)
我必须用new_right_node
初始化None
,否则Rust会出现错误“使用可能未初始化的new_right_node
”。但是,执行此初始化会导致不必要的core::ptr::drop_in_place
调用None
的初始new_right_node
值。手动内联replace_with
,无需预先初始化new_right_node
就可以逃脱:
let new_right_node;
{
let (mut left_node, right_node) = split_binary(orig_node.right.take(), value);
new_right_node = right_node;
mem::swap(&mut orig_node.right, &mut left_node);
debug_assert!(left_node.is_none());
mem::forget(left_node);
}
(Some(orig_node), new_right_node)
因此,如果我可以说服Rust相信闭包将被执行,因此new_right_node
将被初始化,那么看来我可以有效地解决这个问题。
如何有效解决此问题?
答案 0 :(得分:2)
您可以使用unsafe
,因为您知道该值将始终被初始化。有两个unsafe
函数起作用:mem::uninitialized
(禁用“可能未初始化”的错误)和ptr::write
(可让您写入new_right_node
而不删除前一个(未初始化的)错误)值。
unsafe {
let mut new_right_node = std::mem::uninitialized();
orig_node.right.replace_with(|node| {
let (left_node, right_node) = split_binary(node, value);
std::ptr::write(&mut new_right_node, right_node);
left_node
});
}
我相信在没有紧急情况的情况下这是安全的,因为new_right_node
总是被初始化并且right_node
被移入其中而没有一个掉落。但是,要使其处于紧急状态,还必须保证如果new_right_node
发生紧急情况,则无法观察未初始化状态的split_binary
。