如何暗示FnOnce闭包将只执行一次,以避免“捕获可能未初始化的变量”警告?

时间:2018-06-22 10:10:06

标签: rust

我正在尝试实施和应用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将被初始化,那么看来我可以有效地解决这个问题。

如何有效解决此问题?

1 个答案:

答案 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