首页 > 解决方案 > 可变借入带清理的两部分

问题描述

我有一些对象,我想通过可变借用将它们分成两部分,然后在拆分引用超出范围时将它们组合回原始对象。

下面的简化示例是一个Count包含单个 的 struct ,i32我们希望将其拆分为两个s,当两个可变引用超出范围时&mut i32,它们都被合并到原始中。Count

我在下面采用的方法是使用一个中间对象CountSplit,该对象持有对原始Count对象的可变引用,并实现了Drop特征来执行重组逻辑。

这种方法感觉很笨拙。特别是,这很尴尬:

let mut ms = c.make_split();
let (x, y) = ms.split();

let (x, y) = c.make_split().split();不允许在一行中执行此操作,因为中间对象必须具有更长的生命周期。理想情况下,我将能够做类似的事情let (x, y) = c.magic_split();并避免完全暴露中间对象。

有没有一种方法可以做到这一点,不需要let每次都做两个,或者用其他方法来解决这个更习惯的模式?

#[derive(Debug)]
struct Count {
    val: i32,
}

trait MakeSplit<'a> {
    type S: Split<'a>;
    fn make_split(&'a mut self) -> Self::S;
}

impl<'a> MakeSplit<'a> for Count {
    type S = CountSplit<'a>;
    fn make_split(&mut self) -> CountSplit {
        CountSplit {
            top: self,
            second: 0,
        }
    }
}

struct CountSplit<'a> {
    top: &'a mut Count,
    second: i32,
}

trait Split<'a> {
    fn split(&'a mut self) -> (&'a mut i32, &'a mut i32);
}

impl<'a, 'b> Split<'a> for CountSplit<'b> {
    fn split(&mut self) -> (&mut i32, &mut i32) {
        (&mut self.top.val, &mut self.second)
    }
}

impl<'a> Drop for CountSplit<'a> {
    fn drop(&mut self) {
        println!("custom drop occurs here");
        self.top.val += self.second;
    }
}

fn main() {
    let mut c = Count { val: 2 };
    println!("{:?}", c); // Count { val: 2 }

    {
        let mut ms = c.make_split();
        let (x, y) = ms.split();
        println!("split: {} {}", x, y); // split: 2 0

        // each of these lines correctly gives a compile-time error
        // c.make_split();         // can't borrow c as mutable
        // println!("{:?}", c);    //                   or immutable
        // ms.split();             // also can't borrow ms

        *x += 100;
        *y += 5000;
        println!("split: {} {}", x, y); // split: 102 5000
    } // custom drop occurs here

    println!("{:?}", c); // Count { val: 5102 }
}

游乐场

标签: rustlifetimeborrowing

解决方案


我不认为像你这样的临时值的引用可以在今天的 Rust 中工作。

如果有任何帮助,如果您特别想调用带有两个&mut i32参数的函数,就像您在评论中提到的那样,例如

fn foo(a: &mut i32, b: &mut i32) {
    *a += 1;
    *b += 2;
    println!("split: {} {}", a, b);
}

如果您的链接有效,您已经可以使用与您的链接数量相同的行数来做到这一点。

使用链接,您可以调用

let (x, y) = c.make_split().split();
foo(x, y);

如果你只是省略到元组的转换,它看起来像这样:

let mut ms = c.make_split();
foo(&mut ms.top.val, &mut ms.second);

您可以通过例如将可变引用val直接存储在CountSplitas中来使它更漂亮一些first,这样它就变成了foo(&mut ms.first, &mut ms.second);. 如果你想让它感觉更像一个元组,我认为你可以使用DerefMutto 来编写foo(&mut ms.0, &mut ms.1);.

或者,您当然可以将其表述为采用函数的函数

impl Count {
    fn as_split<F: FnMut(&mut i32, &mut i32)>(&mut self, mut f: F) {
        let mut second = 0;
        f(&mut self.val, &mut second);
        self.val += second;
    }
}

然后打电话

c.as_split(foo);

推荐阅读