Мутабельный заимствование на две части с очисткой

У меня есть объект, который я хочу разделить на две части с помощью изменяемого заимствования, а затем объединить их вместе в исходный объект, когда разделенные ссылки выходят за пределы области видимости.

Упрощенный пример ниже предназначен для структуры Count, содержащей один i32, который мы хотим разделить на два &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 }
}

детская площадка:


person Dan R    schedule 15.06.2020    source источник
comment
Для вас важно, чтобы одна из двух частей на самом деле была оригиналом? Вы можете DerefMut в кортеж, если CountSplit просто хранит (i32, i32), где первый является копией оригинала.   -  person nnnmmm    schedule 15.06.2020
comment
@nnnmmm, DerefMut - интересная мысль, но я не уверен, как она решает мою проблему. Дело в том, чтобы разделить на две отдельные изменяемые ссылки, чтобы, например, я мог вызвать функцию с подписью fn foo(a: &mut i32, b: &mut i32);   -  person Dan R    schedule 15.06.2020
comment
Вместо того, чтобы split возвращать две ссылки, возможно, вы могли бы заставить его вызывать закрытие с параметрами x и y: площадка   -  person Jmb    schedule 16.06.2020


Ответы (1)


Я не думаю, что ссылку на временное значение, подобное вашему, можно заставить работать в сегодняшнем 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 непосредственно в CountSplit как first, так что она становится foo(&mut ms.first, &mut ms.second);. Если вы хотите, чтобы он был еще больше похож на кортеж, я думаю, вы можете использовать DerefMut, чтобы иметь возможность писать 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);
person nnnmmm    schedule 16.06.2020