Sorry if this is a stupid question, but I’m struggling with what I think is ownership in rust.

I’m completely new to rust, but have done low level languages before so I know the concept of pointers pretty well. However, this rust ownership I can’t quite wrap my head around.

I’m trying to refactor my solution to AoC Day 4 Part 2 using a struct reference instead of a stand-alone vector.

The error I’m getting, and can’t figure out is in the process function at line

cards.iter_mut().for_each(|card | {

The error is

cannot borrow cards as mutable more than once at a time second mutable borrow occurs here

There is a lot of parsing in my code, so I stuck that in a spoiler below.

The relevant code is:

#[derive(Debug)]
struct Card {
    id: u32,
    score: u32,
    copies: u32,
}

fn process(input: &str) -> u32 {
    let mut cards: Vec = parse_cards(input);

    cards.iter_mut().for_each(|card| {
        let copy_from = card.id as usize + 1;
        let copy_to: usize = copy_from + card.score as usize - 1;

        if card.score == 0 || copy_from > cards.len() {
            return;
        }

        for card_copy in cards[copy_from - 1..copy_to].iter() {
            let id = card_copy.id as usize - 1;
            let add = cards[card.id as usize - 1].copies;
            cards[id].copies += add;
        }
    });

    return cards.iter().map(|c| c.copies).sum();
}

Other code:

spoiler
fn main() {
    let input = include_str!("./input1.txt");
    let output = process(input);
    dbg!(output);
}

fn parse_cards(input: &str) -> Vec {
    return input.lines().map(|line| parse_line(line)).collect();
}

fn parse_line(line: &str) -> Card {
    let mut card_split = line.split(':');
    let id = card_split
        .next()
        .unwrap()
        .replace("Card", "")
        .trim()
        .parse::()
        .unwrap();

    let mut number_split = card_split.next().unwrap().trim().split('|');

    let winning: Vec = number_split
        .next()
        .unwrap()
        .trim()
        .split_whitespace()
        .map(|nbr| nbr.trim().parse::().unwrap())
        .collect();
    let drawn: Vec = number_split
        .next()
        .unwrap()
        .trim()
        .split_whitespace()
        .map(|nbr| nbr.trim().parse::().unwrap())
        .collect();

    let mut score = 0;

    for nbr in &drawn {
        if winning.contains(&nbr) {
            score = score + 1;
        }
    }

    return Card {
        id,
        score,
        copies: 1,
    };
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn full_test() {
        let result = process(
            "Card 1: 41 48 83 86 17 | 83 86  6 31 17  9 48 53
        Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19
        Card 3:  1 21 53 59 44 | 69 82 63 72 16 21 14  1
        Card 4: 41 92 73 84 69 | 59 84 76 51 58  5 54 83
        Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36
        Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11",
        );
        assert!(result != 0);
        assert_eq!(result, 30);
    }
}
  • snaggen@programming.dev
    link
    fedilink
    arrow-up
    6
    ·
    11 months ago

    Why are you doing

    cards.iter_mut().for_each(|card| {
    

    I don’t see that you mutate card anywhere, am I missing something?

    • bia@programming.devOP
      link
      fedilink
      arrow-up
      2
      ·
      edit-2
      11 months ago

      No, that’s probably wrong.

      I’ve just been trying different combinations of borrowing, but I can’t get it to works.

      I’m pretty sure it’s the cards[id].copies += add that is the cause of my issues.

    • bia@programming.devOP
      link
      fedilink
      arrow-up
      2
      ·
      edit-2
      11 months ago

      Thank you. I made the change suggested below to the linked one, but it still fails with the error.

      cannot borrow cards as mutable because it is also borrowed as immutable

  • rutrum@lm.paradisus.day
    link
    fedilink
    English
    arrow-up
    5
    ·
    11 months ago

    Yeah when you iterate over cards mutably, you’re borrowing it again at the line cards[id].copies +=… I think. If you’re going to access the card by index there then you loop over indicies at the top lop instead of iter_mut

    • bia@programming.devOP
      link
      fedilink
      arrow-up
      2
      ·
      11 months ago

      Thanks, I tried that. But now I get the error.

      cannot borrow cards as mutable because it is also borrowed as immutable

  • Gobbel2000@feddit.de
    link
    fedilink
    arrow-up
    4
    ·
    11 months ago

    You’re trying to iterate over a Vec while mutating its contents in other places, which is something the borrow checker doesn’t like. Altough the later cards that get their copies count increased aren’t the same as the iterating reference card, Rust forbids two mutable references into the same Vec, even if they reference different elements of the Vec.

    You could try to iterate over indices into the array instead of directly over array elements, then you get rid of the reference in the outer loop. This would probably require the least change to your code.

    Another option would be to split apart the data structures between what needs to be mutated and what doesn’t. I’ve solved this puzzle in Rust and had a separate mutable Vec for the number of copies of each card. Then you can iterate over and mutate both Vecs separately without having conflicting references.

    • bia@programming.devOP
      link
      fedilink
      arrow-up
      1
      ·
      11 months ago

      I used a separate vector when I first solved it, but wanted to try out this solution as well.

      Using indices worked, thank you!

  • bia@programming.devOP
    link
    fedilink
    arrow-up
    3
    ·
    11 months ago

    OK, so I’m thinking…

    The error says I can’t combine immutable and mutable in the same function. I guess this makes sense, and that it’s because I’m not allowed to mutate an object while accessing it.

    Is this a correct understanding of the error, or is there anything I can do to use this approach?