Before we start writing real world applications with Rust, we have to understand about the Rust’s memory management and how Rust achieves its largest goal, memory safety. So today, we are going to discuss more about these things.

Ownership

In the above examples, we are just trying to assign the value of ‘a’ to ‘b’ . Almost the same code in both code blocks, but having two different data types. And the second one gives an error. This is because of the Ownership.

⭐️ Variable bindings have ownership of what they’re bound to. A piece of data can only have one owner at a time. When a binding goes out of scope, Rust will free the bound resources. This is how Rust achieves memory safety.

⭐️ When assigning a variable binding to another variable binding or when passing it to a function(Without referencing), if its data type is a

Copy Type▸ Bound resources are made a copy and assign or pass itto the function.▸ The ownership state of the original bindings are set to “copied” state.▸ Mostly Primitive types

Move type▸ Bound resources are moved to the new variable binding and we can not access the original variable binding anymore.▸ The ownership state of the original bindings are set to “moved” state.▸ Non-primitive type

🔎 The functionality of a type is handled by the traits which have been implemented to it. By default, variable bindings have ‘move semantics.’ However, if a type implements core::marker::Copy trait , it has a 'copy semantics'.

💡 So in the above second example, ownership of the Vec object moves to “b” and “a” doesn’t have any ownership to access the resource.

Borrowing

But in real life applications, most of the times we have to pass variable bindings to other functions or assign them to another variable bindings. In this case we referencing the original binding; borrow the data of it.

Shared Borrowing (&T)▸ A piece of data can be borrowed by a single or multiple users, but data should not be altered.

Mutable Borrowing (&mut T)▸ A piece of data can be borrowed and altered by a single user, but the data should not be accessible for any other users at that time.

⭐️ And there are very important rules regarding borrowing,

One piece of data can be borrowed either as a shared borrow or as a mutable borrow at a given time. But not both at the same time.

Borrowing applies for both copy types and move types.

The concept of Liveness ↴

💡 Let’s see how to use shared and mutable borrowings in examples.

Examples for Shared Borrowing

Examples for Mutable Borrowing

Lifetimes

When we are dealing with references, we have to make sure that the referencing data stay alive until we are stop using the references.

Think,▸ We have a variable binding, “a”.▸ We are referencing the value of “a”, from another variable binding “x”.We have to make sure that “a” lives until we stop using “x”

🔎 Memory management is a form of resource management applied to computer memory. Up until the mid-1990s, the majority of programming languages used Manual Memory Management which requires the programmer to give manual instructions to identify and deallocate unused objects/ garbage. Around 1959 John McCarthy invented Garbage collection(GC), a form of Automatic Memory Management(AMM). It determines what memory is no longer used and frees it automatically instead of relying on the programmer. However Objective-C and Swift provide similar functionality through Automatic Reference Counting(ARC).

In Rust, ▸ A resource can only have one owner at a time. When it goes out of the scope, Rust removes it from the Memory. ▸ When we want to reuse the same resource, we are referencing it/ borrowing its content. ▸ When dealing with references, we have to specify lifetime annotations to provide instructions for the compiler to set how long those referenced resources should be alive. ▸ ⭐️But because of lifetime annotations make code more verbose, in order to make common patterns more ergonomic, Rust allows lifetimes to be elided/omitted in fn definitions. In this case, the compiler assigns lifetime annotations implicitly.

Lifetime annotations are checked at compile-time. Compiler checks when a data is used for the first and the last times. According to that, Rust manages memory in run time. This is the major reason of having slower compilation times in Rust.

▸ Unlike C and C++, usually Rust doesn’t explicitly drop values at all. ▸ Unlike GC, Rust doesn’t place deallocation calls where the data is no longer referenced.▸ Rust places deallocation calls where the data is about to go out of the scope and then enforces that no references to that resource exist after that point.

💡 Lifetimes are denoted with an apostrophe. By convention, a lowercase letter is used for naming. Usually starts with 'a and follows alphabetic order when we need to add multiple lifetime annotations.

02. On Struct or Enum Declaration▸ Elements with references should attach lifetimes after & sign. ▸ After the name of the struct or enum, we should mention that the given lifetimes are generic types.

03. With Impls and Traits

04. With Generic Types

Lifetime Elision

As I mentioned earlier, in order to make common patterns more ergonomic, Rust allows lifetimes to be elided/omitted. This process is called Lifetime Elision.

💡 For the moment Rust supports Lifetime Elisions only on fn definitions. But in the future it will support for impl headers as well.

⭐️ lifetime annotationsof fn definitions can be elidedif its parameter list has either, ▸ only one input parameter passes by reference. ▸ a parameter with either &self or &mut self reference.

💡In the Lifetime Elision process of fn definitions, ▸ Each parameter passes by reference is got a distinct lifetime annotation.ex. ..(x: &str, y: &str)→..<'a, 'b>(x: &'a str, y: &'b str) ▸ If the parameter list has only one parameter passes by reference, that lifetime is assigned to all elided lifetimes in the return values of that function.ex. ..(x: i32, y: &str) -> &str→..<'a>(x: i32, y: &'a str) -> &'a str ▸ Even it has multiple parameters pass by reference, if one of them has &self or &mut self, the lifetime of self is assigned to all elided output lifetimes.ex. impl Impl{ fn function(&self, x: &str) -> &str {} }→impl<'a> Impl<'a>{ fn function(&'a self, x: &'b str) -> &'a str {} } ▸ For all other cases, we have to write lifetime annotations manually.

'static

⭐️ 'static lifetime annotation is a reserved lifetime annotation. These references are valid for the entire program. They are saved in the data segment of the binary and the data referred to will never go out of scope.

Few more examples about the usage of Rust lifetimes,

OK, Let’s stop the third post of Learning Rust(& Gitbook)series in here. In this post I just tried to summarize about,

🐣 I am a Sri Lankan🇱🇰 Web Developer who lives in Vietnam🇻🇳. So I am not a native English speaker and just learning Rust, If you found any mistake or something need to be changed, even a spelling or a grammar mistake, please let me know. Thanks.

Finally I would like to thank for everyone who helped me at #rust-beginners IRC to understand some missing points. Okay, see you on the next post :)

“The best way to predict your future is to create it.”__ Abraham Lincoln