【问题标题】:Mutable reference to a tuple as input parameter对元组的可变引用作为输入参数
【发布时间】:2017-05-03 14:27:48
【问题描述】:

我似乎掉进了这个被称为“与借用检查器作斗争”的洞里。我有以下功能:

fn draw_pair(decks: &(&mut Deck, &mut Deck)) -> (Card, Card) {
    let (&mut p1, &mut p2) = decks;

    (p1.draw_card(), p2.draw_card())
}

我收到以下错误:

expected type: &(&mut Deck, &mut Deck)
found type: (_, _)

这个想法是对元组的内容进行可变引用。我认为没有理由改变元组本身。该函数将循环运行。

我尝试改写let &(&mut p1, &mut p2) = decks;,但它告诉我它无法移出借来的内容。

这是调用draw_pair的函数:

fn play(decks: (Deck, Deck)) {
    loop {
        let cards = draw_pair(&decks);
        // actual game not yet implemented
    }
}

这也给了我一个错误,说它需要&(&mut Deck, &mut Deck),但得到&(Deck, Deck)

【问题讨论】:

  • 您将 &(Deck, Deck) 传递给您的 draw_pair 函数,而不是预期的 &(&mut Deck, &mut Deck)。
  • @SplittyDev 我知道这一点。
  • @SplittyDev 我手头的数据默认是不可变的
  • 我刚刚更新了我的答案以包含更好的代码版本,没有丑陋的克隆和元组引用。

标签: rust borrow-checker


【解决方案1】:

当您想使用模式匹配和解构获取引用时,请使用ref instead of &。请改用let (ref mut p1, ref mut p2),并取消引用deck

您必须注意,您不能改变不可变数据。您可以使用自己的可变克隆,也可以在没有突变的情况下工作。使play 中的decks 可变是获得对draw_pair 中内部数据的可变引用的唯一方法。下面的代码解决了你的问题:

fn draw_pair(decks: &mut (Deck, Deck)) -> (Card, Card) {
    let (ref mut p1, ref mut p2) = *decks;

    (p1.draw_card(), p2.draw_card())
}

fn play(mut decks: (Deck, Deck)) {
    loop {
        let cards = draw_pair(&mut decks);
        // actual game not yet implemented
    }
}

如果您在play 中收到的decks 对是不可变的,则没有其他办法,只能像@wimh 在他的回答中所做的那样维护您自己的克隆和可变Decks。如果您想更简洁地创建自己的可变克隆,以下一行将有所帮助:&(&mut decks.0.clone(), &mut decks.1.clone())

【讨论】:

    【解决方案2】:

    由于您无法移出借用的可变引用,因此您必须克隆字段。

    fn main() {
        play((&mut Deck(0), &mut Deck(0)));
    }
    
    #[derive(Clone)]
    struct Deck(i32);
    
    fn play(decks: (&mut Deck, &mut Deck)) {
        let cards = draw_pair(&decks);
    }
    
    fn draw_pair(decks: &(&mut Deck, &mut Deck)) -> (i32, i32) {
        let mut p1 = decks.0.clone();
        let mut p2 = decks.1.clone();
        (0, 0)
    }
    

    这是一个保留play 签名的Playpenhere's a version

    老实说,我认为在这种情况下,添加一个新类型来表示元组将是一个更干净和可读的解决方案。

    理想情况下,我会这样做:

    struct Deck {}
    struct Card {}
    
    #[derive(Debug)]
    struct Pair<T> {
        first: T,
        second: T,
    }
    
    impl<T> Pair<T> {
        pub fn new(first: T, second: T) -> Pair<T> {
            Pair { first: first, second: second }
        }
    }
    
    fn play(decks: Pair<Deck>) {
        let mut decks = decks;
        let cards = draw_pair(&mut decks);
    }
    
    fn draw_pair(decks: &mut Pair<Deck>) -> Pair<Card> {
        Pair::new(Card {}, Card {})
    }
    

    【讨论】:

    • 虽然这不是一个理想的解决方案。使这一切正常工作的行let mut decks = decks 是一个糟糕的 hack,应该不惜一切代价避免。它在这么小的应用程序中没有什么不同,但它会在多线程/并发场景中。不可变的东西不应该不惜一切代价改变。
    • 如何不惜一切代价避免这种情况?您拥有 Pair,因此您可以获得对它的可变引用。这永远不会引起任何问题。在多线程场景中,无论如何您都会使用 Cell 或 Mutex 进行同步。或者,您可以将 Pair 更改为 &mut Pair ,这样就可以了。
    【解决方案3】:

    这是第一个错误:

    8 |     let (&mut p1, &mut p2) = decks;
      |         ^^^^^^^^^^^^^^^^^^ expected reference, found tuple
      |
      = note: expected type `&(&mut Deck, &mut Deck)`
      = note:    found type `(_, _)`
    

    解决此问题的最简单方法是取消引用正确的大小 (*decks),但随后会出现另一个错误:

    8 |     let (&mut p1, &mut p2) = *decks;
      |          ^^^^^--
      |          |    |
      |          |    hint: to prevent move, use `ref p1` or `ref mut p1`
      |          cannot move out of borrowed content
    

    正如使用 ref 所暗示的那样,修复了这个问题:

    let (&mut ref p1, &mut ref p2) = *decks;
    

    但是左侧不需要完全解构,也可以使用

    let (ref p1, ref p2) = *decks;
    

    这是一个minimum implementation 来重现您的问题,并应用了修复:

    struct Card{}
    struct Deck{}
    impl Deck {
        fn draw_card(&self) -> Card { Card {}}
    }
    
    fn draw_pair(decks: &(&mut Deck, &mut Deck)) -> (Card, Card) {
        let (ref p1, ref p2) = *decks;
    
        (p1.draw_card(), p2.draw_card())
    }
    
    fn main() {
        println!("Hello, world!");
    }
    

    编辑:这是您的 play 函数的解决方案,无需修改其签名:

    struct Card{}
    
    #[derive(Clone)]
    struct Deck{}
    impl Deck {
        fn draw_card(&self) -> Card { Card {}}
    }
    
    fn play(decks: (Deck, Deck)) {
        loop {
            let (ref deck1, ref deck2) = decks;
            let mut deck1 = deck1.clone();
            let mut deck2 = deck2.clone();
            let decks = (&mut deck1, &mut deck2);
            let cards = draw_pair(&decks);
            // actual game not yet implemented
        }
    }
    
    fn draw_pair(decks: &(&mut Deck, &mut Deck)) -> (Card, Card) {
        let (ref p1, ref p2) = *decks;
    
        (p1.draw_card(), p2.draw_card())
    }
    
    fn main() {
        play((Deck{}, Deck{}));
    }
    

    【讨论】:

    • 需要mut 引用。应该是let (ref mut p1, ref mut p2)
    • @ElectricCoffee 最好提出一个新问题而不是添加更多问题,但我已经为我的答案提供了一个可能的解决方案。由于我不知道您的确切要求,因此我不确定它是否适合您。请考虑包含一个Minimal, Complete, and Verifiable 示例,以便将来重现您的问题。
    • 我知道,坏习惯。但我想我会加上它,因为它与上一个问题有关。有人问我是如何使用它的,所以这就是原因。在不修改类型的情况下真的没有更短的方法吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-08-22
    • 1970-01-01
    • 1970-01-01
    • 2021-06-12
    • 2021-12-23
    • 2014-07-06
    • 1970-01-01
    相关资源
    最近更新 更多