Rust Adventures — A Java programmer understanding Rust Ownership
By: Floriano Victor Peixoto
Tags: Rust, Programming, Software Engineering, Software Development, Technology

Hi there folks!

As you know I’m learning Rust as a 2020 goal and today I decided to learn more about the key feature of the language: Ownership.

Beginning

My career up until now was based solely under Java stacks, so I summed up some experience with Java, Javascript and their libraries. For a long time I heard of the trails of people from C and C++ used to have managing the memory allocation of their programs and how the Garbage Collector saved the day.

The other side of the coin is that I always heard about the low-level languages programmers how the Virtual Machines languages had a poor performance.

For some times when I was writing programs that needed to have a high performance I wanted to use low level language and for the most trivial programs I was just happy to do not have to worry about memory management and for concurrency… well it’s a pain doesn’t matter from where you came if you works with a pre 2010 language.

So for some time the principals solutions we had were:

1 — Low level programming languages with the difficult of memory allocation.

2 — VM languages with GC that removed the memory problem but it had a cost over performance.

Rust comes up with a third option: Ownership. This feature molds everything in the language and is crucial to understand it.

Stack and Heap

People that come from a VM language like Java usually doesn’t have to worry about the Stack or Heap memory because of the GC, although you are working with Strings, so let’s try to summarize it a little bit.

Stack is a memory that the content must have a predefined size and is stored in a ordered manner called LIFO (last in, first out). It’s similar of a pile of plates, you put them on the top and remove from the top. As this memory has a precise size and order it’s fast and secure.

FILO asbtraction

The heap is the opposite, it’s a dynamic area that allocates the values where it fits properly and send the pointer indicating where the value was inserted, this search has a cost and loses performance over stack, but it doesn’t have the necessity to pre define the size of memory a value holds.

In low-level languages it was a task of the programmer to manage what is on the stack and what is on the heap, even when the memory will be free, in a VM language with GC there is no need for the developer to worry about it.

Rust uses ownership to keep track in compile time of what data is on the heap and minimizes problems concerned over it.

But what is the problems we might have?

Let´s follow The Book and use the String as example for that.

Imagine a language that have a Structure to hold a String value, it has some meta-data but the actual value will be allocated over the heap, because this String is mutable and we can’t know it’s value over compile time.

We can define a variable like that:

variable text1 = String(“value”);

When the program runs it will calculate how much memory the text “value” needs and allocate it over the heap, the heap will give the variable a pointer to where this value is, up until now everything good, so the next line comes the following code:

variable text2 = text1;

The language will now create a new structure with metadata to text2, but what about it’s value? How can we solve it?

One solution is to each one have it’s own metadata but share the pointer between them.

What happens when we free from the memory the both variables, let’s say first text2 and after text1? When text2 is released the “value” will be removed from the heap and what will text1 release? Exactly we don’t know and it can cause some problems over the application. It is known as double-free error.

You might be thinking, so let’s duplicate the value or in another words, make a deep copy.

For sure it resolves the double-free error, but remember, the heap is not the fastest memory we have, the search for space and the following of pointer has a cost in performance and we can have a software that is slower than it should be, or that consumes a lot more memory that is advisable.

So what Rust does?

Rust ownership has pretty simple rules but with a lot of impacts over the language. Here are them:

1 — Each value in Rust has a variable that’s called its owner. 2 — There can only be one owner at a time. 3 — When the owner goes out of scope, the value will be dropped.

A scope in Rust is a couple of {}, you can put it anywhere in your code and create a new scope. When it ends the values are dropped as well.

So what about our little example from before? Let’s write it in Rust.

The underline before text2 is just for the compiler stop warning me that it is not been used, so up until now everything is ok:

So why do not print the values to see if everything is ok?

The IDE already warns me that text1 used in the print method is invalid and the compiler confirms that. Do you remember the first and second rules? A value needs a variable as a owner and it will only have one owner per time. When we pass text1 as the value of text2 we are passing the ownership of “value” to it, so after that we cannot use text1 anymore.

Let’s see another example:

Wait, did we just do the same thing with integers and it worked? How that? Do you remember when we talked about the difference between stack and heap? Integer and scalar values has a known size by compiling time, so it goes to stack, because of that Rust implements a trait called Copy that make a copy of the value in stack memory that is almost without cost. If you pay attention the compiler says that String doesn’t have this trait:

When Rust can’t copy a value it moves the value making another variable the owner. So in the String case only text2 has value to release, text1 doesn’t have a value anymore and in the case of Integers each one has a value of their own.

We can call a method clone to solve this, but as we are speaking of heap memory this is not a desirable solution:

Another context is about the scope, the ownership is passed even for parameters because we are changing the scope from one function to another with {}:

As we did before scalar values will be copied as well if you pass as parameter to another function:

The return of a value changes the owner of the value as well, we can use it to move the value inside the function scope and return back to the previous scope:

We have another trick here, there is a literal value for String in rust, it is the &str and it needs a predefined value and size at compile time, so guess what? It implements the Copy trait!

You noted that it starts with &? It is called a reference in Rust. Reference is the way we can use to just borrow the value for another scope but keeps the ownership on the original value.

Rust knows that the rightful owner of “value” is text1 and it is just borrowing to text2 and borrow function, when the scope ends just text1 has a value and just it will release memory.

If the borrow function try to change the value Rust will complain about that:

If you need or want to change the value that the reference holds you need to create a mutable reference with &mut, for that text1 must be declared as mut as well:

But for the sake of memory safety we can have just one mutable reference, for this reason the following code will panic:

If you combine immutables and mutables references the code will panic as well:

With this rust can secure:

1 — Two or more pointers access the same data at the same time. 2 — At least one of the pointers is being used to write to the data. 3 — There’s no mechanism being used to synchronize access to the data.

References are kept alive just until it’s use, so after that the variable is free to be referenced again, even in a mutable reference:

This bring us another compilation problem called Dangling Reference, making reference to dangling pointer, that is a pointer that reference to something invalid. A method that returns a reference of a variable created inside it’s scope, will make a dangling reference.

As we declared before, the value needs a variable as its only owner and by the end of the scope, Rust will release the variable and value from memory, so we are creating a String inside method dagling_reference and return its reference, but when the method finalize the reference will have a pointer to nothing because rust will clean up variable “s” with its value. To fix it we must return a String, with that we move the ownership to another variable.

So for reference we can summarize:

1 — At any given time, you can have either one mutable reference or any number of immutable references. 2 — References must always be valid.

We have slices, they are another data type without ownership that represents a part of a collection, and Strings can be considered a collections of chars. We can make them with &[starting_index..ending_index] notation, in Rust the sequence index start with zero so the minimum value of starting_index can have is 0 and ending_index if exclusive, or in another words it is value higher than the one you want, please note that it is a reference of the values, the rightful owner of the value is the String it’s originated from.

The “..” is the range syntax in Rust, by default it starts from 0 and terminates on the end of collection, so we can change the code above and teh result won’t change:

Note that this is valid only for ASCII characters, if you need to work with multibyte characters it can lead to error. I have a story where I needed to work with graphemes because of that, later I’ll make a more detailed article about this matter.

Everything is OK, but this type &{unknow} is not the most descriptive one so let’s change the code for it’s real return:

As we discussed before the &str is a reference and cannot be changed, with that we can make methods that receives a reference and return another:

As we only use reference that is not moving of ownership of “Medium is great” value, it stays at text1 and end with main method scope. But what happen if we declare text1 as a mutable and tries to clear it?

Do you remember that we cannot have a mutable borrow and an immutable one? It happens with parameters as well, and the Rust compiler panic over that as well. It makes an API easier to use and safe to errors as well.

You might have noticed by now that &str literal is immutable because it is a reference pointing to specific points in binary.

Conclusion

Rust is for sure blazing fast and safe, and it all thanks to Ownership that made possible a mix of performance from low-level language and security of garbage collector ones. With that rust deliver a safe code, very performative one and fun to learn.

I hope you people enjoyed this Story, until next time!