Rust variables are like pets: borrow them wrong and the compiler bites you — but hey, better now than at runtime!
Rust avoids runtime overhead because it doesn’t have a garbage collector and handles memory management at compile time. It ensures there are no data races by enforcing strict rules on how variables are accessed. And since it doesn’t rely on runtime memory management, it’s as fast as C++.
Now, let’s go through the fundamentals of variables in Rust, one by one.By now, you probably already know that Rust moves ownership when we assign a value to a new variable.

So, when we assign the string data in s1 to s2, the memory reference is moved to s2, and s1 is freed immediately — no need to wait for the garbage collector.
But what if we want the s1 to keep the ownership and also wants s2 to have the same value?

We can use the clone() function for that. Here nothings gets freed, reference to both the variables exists. When it does clone, it generally do a deep copy of the data into the heap.
But wait a minute, what’s happening here? Why doesn’t the ownership change in this case?

This is extremely important to note: for simple data types, or what we call primitive data types in Java, a copy is made rather than moving ownership. These types are stored on the stack instead of the heap because their sizes are fixed and small. Also note that copy is done automatically.
Remember: Copy works similar to Clone, but we need to call clone and it creates the new data in heap instead of stack.
Declaring variables, moving them, or cloning them won’t always solve the problem you’re trying to address in your code, nor is it always the best or most optimized approach. In some cases, you may just want to keep the same owner and borrow the variable to perform a task. Rust has carefully considered this situation, offering two types of borrowing: mutable borrowing and immutable borrowing. Let’s understand them with the code below.

Before I explain what’s going on, keep in mind that a struct in Rust is like a blueprint, similar to a class in Java/Scala, but without the OOP properties. The impl part is where the functions live.
As you can see, some of the function parameters start with & and some with &mut. These represent immutable and mutable borrowing, respectively. Use immutable borrowing when you just need to read the data, and mutable borrowing when you need to modify it. In this program, we’re managing student records with the functions add_student, get_student_by_name, and update_student_name. As mentioned earlier, the add and update operations use mutable borrowing so they can modify the students vector inside the School struct, while the get operation uses only immutable borrowing to safely access a student’s data.
Now that we can make things mutable, doesn’t that open the door to data races?
Well, the answer is: Rust only allows one mutable borrow within a given scope. It also doesn’t allow you to mutate a variable if it’s already been immutably borrowed. So the below code doesn’t work


In some scenarios, a variable might still point to a memory address that has already been freed — a situation known as a dangling pointer, which is a common problem in C++. Rust handles this uniquely by catching such issues at compile time, ensuring that dangling pointers never occur.

So, Rust will never allow this to happen. Instead, you should move the ownership, like in the example below:

Wrapping Up
Rust’s approach to variables, ownership, and borrowing might feel strict at first, but that’s exactly what makes it powerful. By catching potential bugs at compile time — like data races or dangling pointers — Rust saves you from tricky runtime issues later. Whether you’re moving, cloning, or borrowing variables, understanding these fundamentals is key to writing safe and efficient Rust code.
Got a question, spotted something I missed, or just want to nerd out about Rust? Feel free to reach out to me on LinkedIn. I’d love to hear from you!


