Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I pass a string to HashSet contains?

I want to use a HashSet for quick string lookup, but I can't seem to find a way to pass a string variable to contains without a compiler error.

refs = HashSet::new();

let first_pass = link_regex.replace_all(&buffer, |caps: &Captures| {

    if caps.len() == 2 {
        refs.insert(caps.at(2).unwrap());
    }

    caps.at(1).unwrap().to_owned()
});

let out = ref_regex.replace_all(&first_pass, |caps: &Captures| {
    let capture = caps.at(1).unwrap().to_owned();

    // only remove if we've seen it before
    if refs.contains(capture) {
        return "".to_string();
    }

    capture
});

That causes this error:

 src/bin/remove_links.rs:30:26: 30:33 error: mismatched types [E0308]
 src/bin/remove_links.rs:30         if refs.contains(capture) {
                                                     ^~~~~~~
 src/bin/remove_links.rs:30:26: 30:33 help: run `rustc --explain E0308` to see a detailed explanation
 src/bin/remove_links.rs:30:26: 30:33 note: expected type `&_`
 src/bin/remove_links.rs:30:26: 30:33 note:    found type `std::string::String`

If I try

refs.contains(&capture)

then I get

src/bin/remove_links.rs:30:17: 30:25 error: the trait bound `&str: std::borrow::Borrow<std::string::String>` is not satisfied [E0277]
src/bin/remove_links.rs:30         if refs.contains(&capture) {
                                           ^~~~~~~~

I'm stumped, do I need to do some sort of type cast?

like image 378
ray Avatar asked Dec 19 '22 14:12

ray


1 Answers

Explanation

First, let's find out what type refs has. At the point HashSet::new(), the compiler can't tell what kinds of things you are going to put into the set, so the type is not clear yet. But the compiler figures it out in this line:

refs.insert(caps.at(2).unwrap());

The expression inside the function call (caps.at(2).unwrap()) returns a &str. So we are putting &strs into the set, thus refs has the type HashSet<&str>.

If you now look at the documentation for contains, you see that it takes some &Q as argument. There are also some bounds: where T: Borrow<Q>, Q: Hash + Eq. We can ignore the Hash + Eq part; it doesn't cause any problems.

So let's focus on the T: Borrow<Q>. We do know what T is: &str. So let's see what impl of Borrow there are for &str: documentation. We'll find many generic impls, the important of which are (removed some noise):

  • impl<T> Borrow<T> for T
  • impl<T> Borrow<T> for &T

So pattern matching our &str with the right hand pattern, we conclude that for &str, Borrow<&str> and Borrow<str> are implemented. So our Q could be str for example. That means that contains receives a parameter of type &str (remember the &Q from above).

capture however, is of type String. &capture is an expression of type &String. Whenever such an expression is used in a position where a &str is needed without doubt, the compiler knows how to turn &String into &str (deref coercion). In this case however, the situation is not that clear, since we take a detour through the Borrow trait. Therefore we have to explicitly convert the String into a &str. There are a quazzilion ways to achieve this, but how about as_str()? So the ...

Working solution

if refs.contains(capture.as_str()) {
    // ...
}
like image 176
Lukas Kalbertodt Avatar answered Jan 04 '23 19:01

Lukas Kalbertodt