在模式匹配中克隆不可克隆对象的可克隆元素

Clone a cloneable element of non-Cloneable object in pattern match

我正在开发一个使用 BTreeMap 和自定义 enum 值的项目。此 enum 不能 #[derive(Clone)],因为某些变体可能包含一个 Clone 不可用的值。我的项目的大致轮廓如下:

enum Foo {
    // Bar has impl Clone, Baz does not.
    // Both Bar and Baz are from external crates,
    // so I cannot impl Clone on Baz.
    A(Result<Vec<Bar>, Baz>),
    B(Bar, Qux),
    C,
}

struct Waldo {
    map: BTreeMap<Bar, Foo>,
    // Other variables...
}

在我的Waldo类比上定义一个方法,我遇到了这样一种情况,在递归函数内部,我使用if let对[=19的结果进行模式匹配=];在这个 if 块中,我正在向 map 添加值。当递归函数的后续迭代看到 map 中的值时,他们知道可以忽略它。像这样:

impl Waldo {
    fn do_something(&mut self, data: Bar) {
        // analyze puts a Foo into map with `data` as a key.
        // It can't return the value AND put it into the map, because
        // Foo would need to be cloneable. Instead...
        self.analyze(data);
        // I let `do_something_else` put the value in the map,
        // and then grab the value *from* the map.
        if let Some(&Foo::A(Ok(ref bar_vec))) = self.map.get(&data) {
            // bar_vec is cloneable, but even if I clone it,
            // `self.map` is still borrowed.

            // 'unique' is filtered so that it only contains
            // elements of bar_vec that aren't a key in `self.map`
            // 'unique' has no dependency on self.map,
            // because the iterator clones all elements
            // before collecting.
            let unique = bar_vec
                .iter() // &Bar
                .filter(|b| !self.map.contains_key(b)) // &Bar, sans those in map
                .cloned() // Bar
                .collect<Vec<Bar>>()

            // Give the newly encountered values a placeholder
            // so that recursions of the function will ignore them
            for new_item in unique.iter().cloned() {
                self.map.insert(new_item, Foo::C); // fails
            }
            // Once the items are inserted with placeholder values,
            // recurse the function to get their real values
            for new_item in unique.into_iter() {
                self.do_something(new_item);
            }
    }

    fn analyze(&mut self, data: Xyzzy) {
        // ...
    }
}

理想情况下,我希望能够在离开 if let 子句之前创建 bar_vec 的克隆,这意味着不再借用 self.map。这是可能的,还是我必须重构我的代码的工作方式?我已经考虑过使 analyze return 成为 Foo 枚举值而不是直接将其添加到地图的选项,并且让 do_something 与 return 匹配value 并将其添加到最后的地图中,但我觉得我也可以 post 这个问题,看看是否可以做一些不那么痛苦的事情。

如果您可以接受夜间编译器,您可以使用 non-lexical 个生命周期。

#![feature(nll)]

use std::collections::BTreeMap;

#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
struct Bar;

// I cannot impl Clone on Baz
struct Baz;

enum Foo {
    A(Result<Vec<Bar>, Baz>),
    B(Bar),
    C,
}

struct Waldo {
    map: BTreeMap<Bar, Foo>,
    // Other variables...
}

impl Waldo {
    fn do_something(&mut self, data: Bar) {
        self.analyze(data.clone());
        if let Some(&Foo::A(Ok(ref bar_vec))) = self.map.get(&data) {
            let unique = bar_vec
                .iter()
                .filter(|b| !self.map.contains_key(b))
                .cloned() 
                .collect::<Vec<Bar>>();

            for new_item in unique.iter().cloned() {
                self.map.insert(new_item, Foo::C); 
            }
            for new_item in unique.into_iter() {
                self.do_something(new_item);
            }
        }
    }

    fn analyze(&mut self, data: Bar) {
        unimplemented!()
    }
}

在当前稳定的 Rust 中,你必须在 if let 范围之外改变 self.map,因为借用是词法的,你不能 "unborrow" 范围内的变量。

    fn do_something(&mut self, data: Bar) {
        self.analyze(data.clone());
        // This allows to access `unique` outside the scope
        // where `self.map` is borrowed
        let unique;
        if let Some(&Foo::A(Ok(ref bar_vec))) = self.map.get(&data) {
            unique = bar_vec
                .iter()
                .filter(|b| !self.map.contains_key(b)) // &Bar, sans those in map
                .cloned() 
                .collect::<Vec<Bar>>();
        } else {
            // early return prevents the use of uninitialized `unique`
            return;
        }
        for new_item in unique.iter().cloned() {
            self.map.insert(new_item, Foo::C); 
        }
        for new_item in unique.into_iter() {
            self.do_something(new_item);
        }
    }

Playground link