Mutable reference to a tuple as input parameter

3.8k views Asked by At

I seem to have fallen into the hole known as "fighting the borrow checker" on this one. I have the following function:

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

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

I get the following error:

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

The idea is to take mutable references the contents of the tuple. I see no reason to mutate the tuple itself. This function will be running in a loop.

I've tried writing let &(&mut p1, &mut p2) = decks; instead, but it tells me it can't move out of borrowed contents.

Here's the function that calls draw_pair:

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

This too gives me an error, saying it expects &(&mut Deck, &mut Deck) but is getting &(Deck, Deck).

3

There are 3 answers

0
EvilTak On BEST ANSWER

Whenever you want to obtain references using pattern matching and destructuring, use ref instead of &. Use let (ref mut p1, ref mut p2) instead, and dereference deck.

You have to note that you cannot mutate immutable data. You can either use your own mutable clones, or work without mutation. Making decks in play mutable is the only way to obtain mutable references to the inner data in draw_pair. The following code solves your problem:

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
    }
}

If the decks pair you receive in play is immutable, there's no other way around it but to maintain your own cloned and mutable Decks like @wimh does in his answer. If you want to create your own mutable clone more concisely, the following one liner will help: &(&mut decks.0.clone(), &mut decks.1.clone())

3
wimh On

This is the first error:

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

The easiest way to fix that is dereferencing the right size (*decks), but then you get the other error:

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

As hinted using ref fixes this:

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

But there is no need to fully destructure the left side, you can also use

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

Here is a minimum implementation to reproduce your problem, with the fix applied:

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!");
}

edit: here is a solution for your play function without modifying it's signature:

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{}));
}
2
SplittyDev On

Since you can't move out of the borrowed mutable references, you'll have to clone the fields.

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)
}

Here's a Playpen and here's a version that preserves the play signature.

Honestly though, I think in this case, adding a new type to represent the tuple would be a more clean and readable solution.

Ideally, I'd do it like that:

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 {})
}