- In the previous section,
we saw how to use vector or vec,
which is one of the standard collection classes,
I should say, structures in Rust.
It turns out there were actually quite a lot
of other structures,
other collection structures you can use
in Rust in the std::collections module.
This one isn't imported by default,
so you'd have to import that into your application
when you first want to use them.
So, for example, in the std::collections module,
we have HashMap, which is a dictionary, key value pairs,
and it uses hashing some kind of mathematical calculation.
When you insert an item, it takes the key,
performs some kind
of mathematical hash algorithm on that key
and that will determine where the item
gets stored within HashMaps memory like a bucket.
And then, when you retrieve the item by key
it can use the hash algorithm to find it quickly.
So, HashMap uses hashing
to find keys quickly in a key value pair.
And then, we have a BTreeMap, which is a binary tree.
So, items organized into a binary tree.
We have each node has an element on the left
and an element on the right,
and when you insert an item into a Btree,
it'll choose where to insert it,
so they can then find it again quickly
using like a binary chop, a binary tree algorithm.
So, we have a HashMap and a binary tree.
Both of which contain key value pairs.
And then, we also have a HashSet and a BTreeSet.
So, a set basically is a unique collection.
If you try to insert the same item twice,
it only stores it once.
And a HashSet uses hashing to find items.
And a BTreeSet obviously uses a binary tree arrangement
to find items quickly, and it doesn't allow duplicates.
So, there are lots of other collection classes
available in the Rust library.
We are gonna have a look at HashMap,
because it's probably the most useful one in practice.
So, HashMap is a dictionary of key value pairs.
Each entry, each row is a key value pair.
The key is usually a simple type like a string,
something you can look up quickly on integer
and the value might be a complete object.
So, for example, a HashMap of employees in a company,
the key might be the employee code
and the value would be the employee object.
You look at the code,
you get back the value for that employee.
So, we got two different ways
to create a HashMap object, two different syntaxes.
In the first syntax here,
I've declared m as a multiple HashMap,
where the key is a string and the value is a integer.
And I've created an empty HashMap here.
In the second case, I've put the type information
on the right-hand side instead of on the left-hand side.
So, m is a multiple HashMap of string i32.
So, one way or the other,
it needs to know the key type
and the value type in the HashMap,
the key type and the value type.
Either of those syntaxes would work.
And by the way, there's no equivalent
to the vec macro that we saw that earlier.
The vec macro can create a vector
and populate it immediately using straightforward syntax.
There is no equivalent for HashMap.
You literally have to create an empty HashMap first,
and then add items to it later.
So, to add an item to a HashMap,
you can call the insert function.
Give it the key and the value.
If the key doesn't exist, then it gets inserted.
If the key does exist, then the value gets overwritten.
So, this really is more like an upsert.
It'll update the key in the value if the key already exists.
It'll insert the key
in the value if it doesn't already exist.
If you want to guard against overwrite an item
that is already there, only insert it
if it isn't already there,
then you can use this approach instead.
A little bit more lengthy. You take your HashMap.
The HashMap structure has an entry function,
which is like it's an enum, actually.
It gives it back an entry enum.
And that entry enum tells you whether the key exists or not.
And that if it doesn't exist, then it'll insert this value.
So, if you want to explain it in simple terms.
Get the entry
and if the entry doesn't exist, then insert this value,
but only if the entry doesn't already exist.
If it does exist, it doesn't get overwritten.
So, that's quite useful actually.
To get a value for a key, you can use square brackets.
You take your HashMap and you use square brackets.
You give it the key and hopefully you'll get back the value,
the actual value, like an integer 32.
That will perform a check.
And if the key is missing, then your program crashes.
It panics, key not found.
It's like an exception. Your program terminates.
So, that's not great.
If you want the more forgiven lookup
where it's allowed not to be there,
then you can use the get function instead.
This will look up the key safely.
And as you would probably expect by now,
when you call the get function, give it the key,
it returns an option,
which may contain the value
for the key if it exists or none.
So, you then take this option
and you'd use a match statement to see where you stand.
You can say this option that was returned
when I looked at the key, does it contain some value?
What is the value for that key? In other words.
And it'll give you the value
that corresponded to the key.
Otherwise, if the key was missing,
if there's an invalid key, then you get back none.
And that tells you
there is no such key, no such value either.
Right.
You can iterate over items
in a HashMap using this kind of syntax, quite familiar.
On each iteration, you'll get back an entry
and the entry gives you the key
and the value for the current row.
Notice again, the ampersand
when we use the for loop over HashMap,
as with a vector actually,
the ampersand indicates borrowing.
It means that we're referring to the items as we iterate.
We're not destroying the items as we iterate.
If you accidentally forgot the ampersand,
then the map would be drained as you iterate.
The items would actually be removed from the collection,
which isn't what you want at all.
Probably not anyway.
So, remember the ampersand there.
You're basically taking a reference to
or borrowing the map without actually destroying it.
So, it's a non-destructive iteration. Very important.
Right.
So, the HashMap structure implements the debug interface.
So, we can use debug output like this,
which is actually very useful.
You can take a HashMap
and you can run it through debug format
and it'll print all the items,
the key value pairs for each item,
for each entry in the HashMap.
Okay. So, I think it's time for an example.
Let's go back to our project.
Lesson five, compounds collections.
This is the last example we're gonna look at in this lesson.
Demo maps.
Right. So, here's our code.
Notice that I've introduced
the HashMap structure into scope.
If I didn't do this, it's not automatically in scope.
So, in the absence of that use statement,
in my code, whenever I refer to HashMap,
instead, I'd have had to have said std::,
excuse me, std::collections::HashMap.
That's gonna get quite tiresome quite quickly.
So, let's just bring it into scope
in the top of our program.
That means we can now use the HashMap name directly,
which is much better.
Right. So, there's the code.
Let me run it, so you can actually see the output,
and then we'll discuss.
(keys clacking)
Cargo run.
I've made the same mistake as before.
I can't believe this.
I forgot to actually run the function.
So, let's do that. Does help.
Let's run that again.
Right. So, let's see then.
So, using maps, here we are.
Good.
So, I've created a multiple HashMap using that syntax.
The key is a string.
So, it's gonna be a country code in this example.
The value will be the telephone code for that country.
So, I can either use that syntax
to create my HashMap of string and init,
or I could use this syntax
to create my HashMap of string and init.
I'm not gonna use m2 in my demo, hence, the underscore.
To avoid getting warnings about unused variables.
m is going to be what I'm gonna use.
So, I insert a new string.
I'm gonna explain later on,
I am gonna discuss what this actually means.
String::from.
Technically, that's how you create a string object in Rust.
So, that's what you need to do.
I'll talk about why later on.
So, that's basically the UK.
The telephone code for the UK is 44.
The telephone code for Norway,
I do a lot of work in Norway, 47.
And Singapore. The telephone code for Singapore is 65.
And here, insert only if not present already
a key, South Africa.
And the international dialing code for South Africa is 27.
So, if SA was already present,
then this statement would have no effect.
It only inserts if the key isn't already there,
which it isn't at the moment.
So, that will actually do the insertion.
Look up a key, look up the UK, you'll get back the value.
It looks up UK, it is there.
Good. And it'll gimme back the value 44.
And that's the value printed here. Very good.
And then, obviously, what I call the get function,
that's gonna gimme back some value,
because UK is present in my HashMap
and the value again is going to be 44,
like so.
And then, to iterate over the items
in HashMap, remembering again,
the importance of the borrowing syntax.
All will be revealed later.
It'll output.
When you output an entry,
the entry structure,
I feel like it also implements debug formatting.
So, you can just output an entry in one go.
And each entry displays itself like this,
the key and the value.
When you iterate over a HashMap,
the order in which you get items back isn't predictable.
It's not a sequential collection
where with a vector, when you insert at the beginning,
you get it back at the beginning.
But with a HashMap, it uses hashing
to determine the sequence inside the HashMap.
You can never really predict the order
you're gonna get back items when you iterate.
You don't care either. You don't really care.
What you really care about is you can look up a key
and get back the value.
When you iterate over HashMap,
who knows the order in which the items are gonna come back.
It depends on the hashing algorithm.
So, I gap up all the keys and values,
but not necessarily in the order
that I would've expected maybe.
Okay, finally then, output the whole HashMap in one go.
So, the HashMap will output Norway, 47.
South Africa, 27, Singapore, 65, UK, 44.
So, it uses that kind of syntax.
That syntax looks reminiscent of a...
Or an object in JSON or a dictionary in Python
and I don't think that's accidental.
Okay, one last thing.
What if I look up a key
that's actually not present in my HashMap?
If I look up the key for,
let's say Switzerland, CH,
Switzerland isn't listed in my HashMap, so
well this get will be okay, it'll just say no value.
Let's just try that.
So, it gives me a no value output there.
I mean, that's the whole point
of returning the option, isn't it?
That it can fail gracefully.
But if I use the square brackets
and I give it an invalid key, at this point, it'll panic.
In other words, it'll raise an error
and terminate on line 131.
(keys clacking)
Okay, so let's have a look.
Oh, yes, I did envisage this come in.
My main thread has panicked,
crashed, in other words.
No entry found for key, and it was on line 131.
So, I'd go back in here and I'd fix it.
NO for Norway.
Let's run it again.
And this time,
it'll give me the international dialing code for Norway, 47.
Good. Fantastic.
So, that concludes the lesson.
We've seen how to use arrays and tuples.
An array is a fixed size homogeneous collection.
A tuple is a fixed size heterogeneous collection
with different types of elements.
A vector is like a resizeable array.
And a HashMap is like a dictionary
where you can look up keys and get the values back.
Okay, there are other collection classes as well.
Have a look at the std::collections module
for a list of all the available structures.