rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
98.32k stars 12.72k forks source link

confusion when intersecting multiple HashSets #105276

Open briandorsey opened 1 year ago

briandorsey commented 1 year ago

Context: I'm about a month in to learning Rust, doing Advent of Code to help learn. Day three this year has a problem where part of the solution is a nice fit for intersecting three HashSets. Here is the code I ended up with.

I've extracted a minimal version of the error I got stuck at. I spent... an embarrassingly long time trying to get something working with .intersection(). This is what I expected to work based on understanding sets from other languages and reading the HashSet docs (and seeing lots of chained function calls in Rust).

playground

use std::collections::HashSet;

fn main() {
    let a: HashSet<_> = "vJrwpWtwJgWrhcsFMMfFFhFp".chars().collect();
    let b: HashSet<_> = "jqHRNqRjqzjGDLGLrsFMfFZSrLrFZsSL".chars().collect();
    let c: HashSet<_> = "PmmdzqPrVvPwwTWBwg".chars().collect();

    let in_all = a
        .intersection(&b)
        .collect::<HashSet<_>>()
        .intersection(&c)
        .collect::<HashSet<_>>();

    println!("{in_all:?}");
}

The current output is: (all output is from 1.65.0 stable)

error[[E0308]](https://doc.rust-lang.org/stable/error-index.html#E0308): mismatched types
  --> src/main.rs:11:23
   |
11 |         .intersection(&c)
   |          ------------ ^^ expected `&char`, found `char`
   |          |
   |          arguments to this function are incorrect
   |
   = note: expected reference `&HashSet<&char>`
              found reference `&HashSet<char>`
note: associated function defined here

For more information about this error, try `rustc --explain E0308`.

I didn't understand how to easily affect the char references inside the HashSets.

I eventually ended up here, which works... but I knew wasn't right, and wouldn't work well for a larger number of sets.

use std::collections::HashSet;

fn main() {
    let a: HashSet<_> = "vJrwpWtwJgWrhcsFMMfFFhFp".chars().collect();
    let b: HashSet<_> = "jqHRNqRjqzjGDLGLrsFMfFZSrLrFZsSL".chars().collect();
    let c: HashSet<_> = "PmmdzqPrVvPwwTWBwg".chars().collect();

    let tmp1: HashSet<&char> = a.intersection(&b).collect();
    let tmp2: HashSet<&char> = b.intersection(&c).collect();
    let in_all: HashSet<&&char> = tmp1.intersection(&tmp2).collect();

    println!("{in_all:?}");
}

I don't have specific proposed output, but I would have loved hints that lead me to one of the two solutions below.

I looked at some other folks solutions to the same problem notes and links here and with the caveat that I'm early in my Rust learning, here are the two outcomes I liked best:

Solution A

The solution I think I like best involves using operator sugar: HashSet implements &, which is clean and nearly what I expected intersection to work like. It's still awkward to give the extra &() reference in order to make the third intersection work. But, I'm probably missing some nice Rsut syntax, and I imagine there is a nice way to do this for the general case of a sequence of iteration. Maybe .fold() over an iterator or something?

playground

use std::collections::HashSet;

fn main() {
    let a: HashSet<_> = "vJrwpWtwJgWrhcsFMMfFFhFp".chars().collect();
    let b: HashSet<_> = "jqHRNqRjqzjGDLGLrsFMfFZSrLrFZsSL".chars().collect();
    let c: HashSet<_> = "PmmdzqPrVvPwwTWBwg".chars().collect();

    let in_all = &(&a & &b) & &c;

    println!("{in_all:?}");
}

output:

{'r'}

Solution B

Looking at other solutions, lots of folks used .copied()... and just adding that almost works... and the error message guides me to a working solution. (The message when adding .cloned() is similarly good)

playground

use std::collections::HashSet;

fn main() {
    let a: HashSet<_> = "vJrwpWtwJgWrhcsFMMfFFhFp".chars().collect();
    let b: HashSet<_> = "jqHRNqRjqzjGDLGLrsFMfFZSrLrFZsSL".chars().collect();
    let c: HashSet<_> = "PmmdzqPrVvPwwTWBwg".chars().collect();

        let in_all = a
            .intersection(&b)
            .copied()
            .collect::<HashSet<_>>()
            .intersection(&c)
            .collect::<HashSet<_>>();

    println!("{in_all:?}");
}

output:

error[[E0716]](https://doc.rust-lang.org/stable/error-index.html#E0716): temporary value dropped while borrowed
  --> src/main.rs:8:22
   |
8  |           let in_all = a
   |  ______________________^
9  | |             .intersection(&b)
10 | |             .copied()
11 | |             .collect::<HashSet<_>>()
   | |____________________________________^ creates a temporary which is freed while still in use
12 |               .intersection(&c)
13 |               .collect::<HashSet<_>>();
   |                                       - temporary value is freed at the end of this statement
14 |
15 |       println!("{in_all:?}");
   |                  ------ borrow later used here
   |
help: consider using a `let` binding to create a longer lived value
   |
8  ~         let binding = a
9  +             .intersection(&b)
10 +             .copied()
11 +             .collect::<HashSet<_>>();
12 ~         let in_all = binding
   |

For more information about this error, try `rustc --explain E0716`.

And that works!:

playground

use std::collections::HashSet;

fn main() {
    let a: HashSet<_> = "vJrwpWtwJgWrhcsFMMfFFhFp".chars().collect();
    let b: HashSet<_> = "jqHRNqRjqzjGDLGLrsFMfFZSrLrFZsSL".chars().collect();
    let c: HashSet<_> = "PmmdzqPrVvPwwTWBwg".chars().collect();

    let binding = a.intersection(&b).copied().collect::<HashSet<_>>();
    let in_all = binding.intersection(&c).collect::<HashSet<_>>();

    println!("{in_all:?}");
}

output:

{'r'}
leonardo-m commented 1 year ago

Advent of Code shows some of its usefulness :-)