Part 3: Methods, Options, References and Head Nodes
For the last 2 days and today I have dealt with various topics. Since I somehow slipped one topic into another, it makes little sense made to write individual posts. I had to tie the head knot first solve that went along with the various topics 😇.
I’ve put together several examples that reflect quite well that what I’ve learned in the past few days. But let’s just start.
Rust, referencing and ownership
I don’t want to go into too much detail here. I think the Rust book can explain all of this in much better detail than I can. Just this much: What you know as a pointer from languages like Go or C ++ works differently in Rust. With its Owenership System, Rust ensures that every asset has only one owner. However, values can still be referenced. To do this, they are “borrowed” and then returned to the owner.
Let’s just start with an example. You can find the complete code here:
Lifetimes
In my example I created a data structure for a person. This person should have a name and a list of friends. I defined the name as & str string slice, which is a reference to the storage location. If you are interested in details about strings, you should read this article. This explains exactly which strings are stored and referenced and what the differences are.
|
|
Here the compiler gives us an error:
|
|
Rust wants a lifetime specification. This is because Rust doesn’t know how long the & str references should be valid. By default, the memory is released in Rust after leaving a block “{}” and this would invalidate these references. We use the lifetimes to tell the Rust compiler how long the references should be valid:
|
|
We declare a lifetime a for our data structure here. We also assign these to the & str reference of the name and the elements of the friends list. By doing this, we are telling the compiler that the fields should exist as long as the person exists.
When implementing the methods, the compiler also requests lifetimes again:
|
|
There are only two simple methods here. new creates a new person and is not strictly a method. The second method adds friends to the friends list.
Match and if let
The friends structure field is an option field, i.e. the field can either contain a list of friends or nothing (None). If you want to use the content of the field, you usually have to distinguish whether the field is occupied or not. Help match:
|
|
In the event that friends contains data, Peter has friends is output, otherwise nothing happens.
The same thing can be achieved more compactly by replacing the match block with if let:
|
|
Both variants lead to the same result. In this case, however, there are no friends. If the option field is occupied for a person, there is also an output:
|
|
Here, too, the use of if let is more compact, although I personally find that the match block gives a clearer understanding of what is being done here.
Referencing and changing data
Let’s say we want to change a friend in the example above. In this case the friends list must be mutable / changeable:
|
|
By using the as_mut () method, our friend list is passed to had_friends as mutable and we can change the 2nd element. But if we do that, another problem arises. has_friends becomes the new owner of the friends list. That means, if we want to access max.friends, the compiler reports an error:
|
|
Error message:
|
|
The compiler tells us that the max_friends content has been moved to has_friends. The compiler also states that the Vector copy trait will not be implemented. At the same time, the compiler suggests part of the solution. We have to lend max.friends to has_friends.