0% found this document useful (0 votes)
13 views1 page

Understanding Ownership

Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
13 views1 page

Understanding Ownership

Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 1

Understanding Ownership

Ownership is Rust's most unique feature and has deep implications for the rest of the
language. It enables Rust to make memory safety guarantees without needing a garbage
collector.

What is Ownership?
Ownership is a set of rules that govern how a Rust program manages memory. All programs
have to manage the way they use a computer's memory while running.Unlike other
languages Rust uses a different approach, it manages memory through a system of
ownership with a set of rules that the complier checks. If any of the rules are violated, the
program won't compile.

Ownership Rules
• Each value in Rust has an owner.
• There can only be one owner at a time.
• When the owner goes out of scope, the value is dropped.
Variable Scope
A scope is the range within a program for which an item is valid. For example

let s = "hello";

The variable s refers to a string literal, where the value of the string is hardcoded into the text
of our program. The variable is valid from the point at which it's declared until the end of for
the current scope.

{ // s is not valid here, since it's not yet declared


let s = "hello"; // s is valid from this point forward
// do stuff with s
} // this scope is now over, and s is not longer valid

There are two important points in time here:


• When s comes into scope, it is valid
• It remains valid until it goes out of scope.
String Type
To illustrate the rules of ownership, we need a data type that is more complex than those we
covered in "Data Types". We want to look at data that is stored on the heap and explore how
Rust knows when to clean up that data, and String type is a great example.We've already
seen string literals, where a string value is hardcoded into our program. String literals are
convenient, but they aren't suitable for every situation. One reason is that they're immutable.
Another is that not every string value can be known when we write our code. For these
situations, Rust has a second string type, String.We can create a String from a string literal
using the from function, like so:

let s = String::from("hello");

The double colon :: operatory allows us to namespace this particular from function under the
String type.This kind of string can be mutated:

let mut s = String::from("hello");


s.push_str(", world!");
println!("{s}");

Memory allocation
In the case of a string literal, we know the contents at compile time, so the text is hardcoded
directly into the final executable.With the String type, in order to support a mutable,
growable piece of text, we need to allocate an amount of memory one the heap, unknown
at compile time, to hold the content.This means:

• The memory must be requested from the memory allocator at runtime.


• We need a way of returning this memory to the allocator when we're done with it.
The first part is done by us: When we call String::from its implementation requests the
memory it needs.However, second part is different, In Languages with garbage collector(GC)
the GC keeps track of and cleans up memory that is not being used anymore, and we don't
need to think about it. In most languages without a GC, it's our responsibility to identify when
memory is no longer being used and to call code to explicitly free it. If we forget, we'll waste
memory. If we do it too early, we'll have an invalid variable. If we do it twice, that's a bug too.
We need to pair exactly one allocate with exactly one free.

Rust takes a different path: the memory is automatically returned once the variable that owns
it goes out of scope. Rust calls a special function for us when variable goes out of scope. This
function is called drop, and it's where the author of String can put the code to return the
memory. Rust calls drop automatically at the closing curly bracket.
Variable and Data Interreacting with Move
Multiple variables can interact with the same data in different ways in Rust.

let x = 5;
let y = x;

We can probably guess what this is doing: "bind the value 5 to x; then make a copy of the
value in x and bind it to y." This is indeed what is happening, because integers are simple
values with a known, fixed size, and these two 5 values are pushed onto the stack.
Now let's look at the String version:

let s1 = String::from("hello");
let s2 = s1;

This looks very similar, so might assume that the way it works would be same, But this isn't
quite what happens.
A String is made up of three parts, a pointer to the memory that holds the contents of the
string, a length, and a capacity. This group of data is stored on the stack. Content is stored on
heap memory.When we assign s1 to s2, the String data is copied, meaning we copy the
pointer, the length, and the capacity that are on the stack. We do not copy the data on the
heap that the pointer refers to.

Earlier, we said that when a variable goes out of scope, Rust automatically calls the drop
function and cleans up the heap memory for that variable. when s2 and s1 go out of scope,
they will both try to free the same memory. This is known as a double free error. Freeing
memory twice can lead to memory corruption, which can potentially lead to security
vulnerabilities.
The concept of copying the pointer, length and capacity without copying the data probably
sounds like making a shallow copy, But because Rust also invalidates the first variable, instead
of being called a shallow copy, it's known as a move.That solves our problem!

Variables and Data Interacting with Clone


If we want to deeply copy the heap data of the string, not just the stack, we can use a
common method called clone.Here's an example of the clone method in action:

let s1 = String::from("hello");
let s2 = s1.clone();

Stack-Only Data: Copy


There's another wrinkle we haven't talked about yet.

let x = 5;
let y = x;

But this code seems to contradict what we just learned. The reason is that types such as
integers that have a known size at compile time are stored entirely on the stack, so copies of
the actual values are quick to make.
Rust has a special annotation called the Copy trait that we can place on types that are stored
on the stack, as integers are. If a type implements the copy trait, variables that use it do not
move, but rather are trivially copied, making them still valid after assignment to another
variable.
Here are some of the types that implement Copy:
• All the integers types
• The Boolean type
• All the floating-point types
• The character type, char.
• Tuples, if they only contains type that also implement Copy.
Ownership and Functions
The mechanics of passing a value to a function are similar to those when assigning a value to
a variable. Here is an example with some annotations showing where variables go into and
out of scope.
Return values and Scope
Returning values can also transfer ownership.

fn main() {
let s1 = gives_ownership();
let s2 = String::from("hello");
let s3 = takes_and_gives_back(s2);
}

fn gives_ownership() -> String{


let some_string = String::from("yours");
some_string
}

fn takes_and_gives_back(a_string: String) -> String {


a_string
}

The ownership of a variable follows the same pattern every time.While this works, taking
ownership and then returning ownership with every function is bit tedious. What if we want
to let a function use a value but not take ownership?
Rust has a feature for using a value without transferring ownership, called references.

References and Borrowing


A reference is like a pointer in that it's an address we can follow to access the data stored at
that address; that data is owned by some other variable. Unlike a pointer, a reference is
guaranteed to point to a valid value of a particular type for the life of that reference.
Here is how you define and use reference :

fn main(){
let s1 = String::from("hello");
let len = calculate_length(&s1);

println!("The length of '{s1}' is {len}.");


}

fn calculate_length(s: &String) -> usize {


s.len()
}

The &s1 syntax lets us create a reference that refers to the value of s1 but does not own it.
Because it does not own it, the value it points to will not be dropped when the reference
stops being used.Likewise, the signature of the function uses & to indicate that the type of
the parameter s is reference.We call the action of creating reference borrowing.Just as
variable are immutable by default. so are references.

Mutable References
We can modify a borrowed value using mutable references.

fn main() {
let mut s = String::from("hello");
change(&mut s);
}

fn change(some_string: &mut String){


some_string.push_str(", world");
}

Mutable references have one big restriction: if you a mutable reference to a value, you can
have no other references to that value.
Dangling References
In Rust, the complier guarantees that references will never be dangling references: If you
have a reference to some data, the complier will ensure that the data will not go out of
scope before the reference to the data does.
The Rule of References
• At any given time, you can have either one mutable reference or any number of
immutable references.
• References must always be valid.

The Slice Type


Slices let you reference a contiguous sequence of elements in a collection rather then the
whole collection. A Slice is a kind of reference, so it Does not have ownership.
Here's a small programming problem: write a function that takes a string of words and returns
the first word it finds in the string.We can solve this problem easily with the help of String
Slices.
String Slices
A string slice is a reference to part of a String, and it looks like this:

let s = String::from("Hello world");

let hello = &s[0..5];


let world = &s[6..11];

We create slices using a range within brackets by specifying [starging_index..ending_index]


, where starting_index is the first position in the slice and ending_index is one more than
the last position in the slice. Internally, the slice data structure stores the starting position and
the length of the slice, which corresponds to ending_index minus starting_index.

With Rust's .. range syntax, if we want to start at index 0, we can drop the value before the
two periods.

let s = String::from("hello");

let slice = &s[0..2];


let slice = &s[..2];

By the same token, if our slice includes the last byte of the String, we can drop the trailing
number.

let s = String::from("hello");

let len = s.len();


let slice = &s[3..len];
let slice = &s[3..];

we can also drop both values to take a slice of the entire string.

let slice = &s[..];

With all this information in mind, let's write first_word to return a slice. The type that signifies
"string slice" is written as &str:

fn first_word(s : &String) -> &str{


let bytes = s.as_bytes();

for (i, &item) in bytes.iter().enumerate(){


if item == b' ' {
return &s[0..i];
}
}

&s[..]
}

You might also like