Single or double reference to string?

I'm dumbfounded, this doesn't work:

let required_fields: HashSet<&str> =
        HashSet::from_iter(&["byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid"]);

It says:

expected `str`, found `&&str`
   |
   = note: expected struct `HashSet<str, RandomState>`
              found struct `HashSet<&&str, _>`

OK.

Why would it expect str if I clearly specify this should be a HashSet of &str?

And even then why would it get &&str if the type of a string literal is &str?

Utterly incomprehensible.

Can you post more of your code? When I put this in the playground, I get this error instead, which makes more sense:

error[E0308]: mismatched types
 --> src/main.rs:6:9
  |
5 |     let required_fields: HashSet<&str> =
  |                          ------------- expected due to this
6 |         HashSet::from_iter(&["byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid"]);
  |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `str`, found `&str`
  |
  = note: expected struct `HashSet<&str, RandomState>`
             found struct `HashSet<&&str, _>`

(Update to come re: why this error happens)

I mean this is a fairly standalone literal at the start of a function? The rest is a sketch I'm doing for advent of code.

This may be better but for me it still contains the same incomprehensible thing.

The IntoIterator implementation for &[T] produces items of type &T. Because quoted string literals have the type &str, the type yielded by your iterator is &&str:

impl<'a, T> IntoIterator for &'a [T] {
    type Item = &'a T;
    /* ... */
}

Slices are unsized, so there's no way to pass them by value; this prevents the creation of an owning iterator. The simplest fix is to build a Vec<&str> and consume it to produce your HashMap:

let required_fields: HashSet<&str> = HashSet::from_iter(
    vec!["byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid"]
);

/* or  */

let required_fields: HashSet<&str> = vec![
    "byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid" 
].into_iter().collect();

Alternatively, because &str implements Copy (i.e. is trivially copyable), you can use the .copied() iterator adapter to remove the extra reference:

let required_fields: HashSet<&str> = HashSet::from_iter(
    ["byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid"].iter().copied()
);

/* or */

let required_fields: HashSet<&str> = [
    "byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid"
].iter().copied().collect();

Finally, depending on the problem, you might want/need to store editable owned Strings in the map instead of immutable &strs:

let required_fields: HashSet<String> = [
    "byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid"
].iter().copied().map(String::from).collect();
4 Likes

Good to know. If I go to the documentation you linked there, I have no idea how what you write follows from that.

It also has this: type Item = &'a T but it's unclear what Item is referencing.

No idea what that means.

That's exactly what it means. If you use &[T] as an iterator, the items yielded by the iterator will have type &T.

What am I missing that would make me able to understand that?

I'm not sure. Are you familiar with generics? If &[T] yields elements of type &T, and you have a &[&str], then T == &str and so &T == &&str.

You can also ask the question "what type is it at all possible for a slice to yield", which is easy to answer if you know what a slice is: it's a pointer to a contiguous chunk of identically-typed elements. Since it doesn't have ownership of the pointed elements, it can only yield pointers to them when iterated over.

4 Likes

I'm familiar with generics and I understand the answer.

I just don't see how the answer follows from the linked docs.

<[T]>::IntoIterator defines Item=&T and the <HashMap as FromIterator>::from_iter() builds a collection from elements of type Iter; this latter fact can be deduced from the FromIterator<(K, V)> impl signature of the hash map.

OK. That is a lot more human understandable than anything I've found thusfar. Thanks!

1 Like

It is about trait definitions. It allows the person who defines trait to specify return values of methods in a generic way.

OK. I think I'm getting it but I haven't reached chapter 19 yet.

1 Like
Aside re: terminology

There's some inconsistency in the usage of the term slice. In the most formal settings it's [T], an arbitrary-length chunk of memory, full of Ts. Due to the way the Rust compiler currently works, there's very little that you can do with [T] directly-- You can't even store it in a variable.

In almost all cases, you're instead actually working with a reference to a slice, &[T] or &mut [T]. Because they're so common, these are also sometimes what is meant by slice, as @H2CO3 does here.

1 Like

I linked to the portion of the documentation that says what type the particular iterator you're working with will produce, as that's usually the information people have trouble finding. To find out what Item means in this context, you need to click through to the main IntoIterator documentation.

From there, you can figure out what Item means in a few ways. The most direct is to scroll down to the Associated Types section, which has an entry for Item:

The type of the elements being iterated over.

You can also look at the declaration of IntoIterator at the top of the page:

pub trait IntoIterator 
where
    <Self::IntoIter as Iterator>::Item == Self::Item, 
{
    type Item;
    type IntoIter: Iterator;

    fn into_iter(self) -> Self::IntoIter;
}

The key point here is the where clause, which says that Item is the same as the one defined by the Iterator implementation of the IntoIter type, so you can get more information by looking at the Iterator documentation. It provides the same description for Item as IntoIterator did, but you also can find some methods here that use the defined type. In particular:

fn next(&mut self) -> Option<Self::Item>

Advances the iterator and returns the next value.

Returns None when iteration is finished. Individual iterator implementations may choose to resume iteration, and so calling next() again may or may not eventually start returning Some(Item) again at some point.

So, whenever you ask the iterator for an item, it will be of type Item.


This isn't an easy process, but, in practice, you don't need to follow it very often. Most traits are significantly simpler than the iterator-related ones.

3 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.