2 Memory, variables, and ownership

This chapter covers

In this chapter, you’ll see how Rust keeps you thinking about the computer itself. Rust keeps you focused on how the computer’s memory is being used for your program and what ownership is (who owns the data). Remember this word, ownership—it’s probably Rust’s most unique idea. We’ll start with the two types of memory a computer uses: the stack and the heap.

Oh, and there’s quite a bit more to learn about printing to build on what you learned in the last chapter. Look for that at the end!

2.1 The stack, the heap, pointers, and references

Understanding the stack, the heap, pointers, and references is very important in Rust. We’ll start with the stack and the heap, which are two places to keep memory in computers. Here are some important points to keep in mind:

Pointers sound complicated, but they don’t have to be. Pointers are like a table of contents in a book. Take this book, for example.

This table of contents is like five pointers. You can read them and find the information they are talking about. Where is the chapter “My life”? It’s on page 1 (it points to page 1). And where is the chapter “My job”? It’s on page 23.

The pointer you usually see in Rust is called a reference, which you can think of as a memory-safe pointer: a reference point to owned memory and not just unsafe random memory locations. The important thing to know about a reference is this: a reference points to the memory of another value. A reference means you borrow the value, but you don’t own it. It’s the same as our book: the table of contents doesn’t own the information. The chapters own the information. In Rust, references have a & in front of them:

You read my_reference = &my_variable like this: “my_reference is a reference to my_ variable” or “my_reference refers to my_variable.” This means that my_reference is only looking at the data of my_variable; my_variable still owns its data.

You can even have a reference to a reference or any number of references:

fn main() {
    let my_number = 15;                        
    let single_reference = &my_number;         
    let double_reference = &single_reference;  
    let five_references = &&&&&my_number;      
}

This is an i32.

This is an &i32.

This is an &&i32.

This is an &&&&&i32.

These are all different types, just in the same way that “a friend of a friend” is different from “a friend.” In practice, you probably won’t see references that are five deep, but you will sometimes see a reference to a reference.

2.2 Strings

Rust has two main types of strings: String and &str. Why are there two types, and what is the difference?

Also, note that &str has the & in front of it because you need a reference to use a str for the same previously discussed reason: the stack needs to know the size, and a str can be of any length. So we access it with a &, a reference. The compiler knows the size of a reference’s pointer, and it can then use the & to find where the str data is and read it. Also, because you use a & to interact with a str, you don’t own it. But a String is an “owned” type. We will soon learn why that is important to know.

Both &str and String are encoded with UTF-8, which is the main character-encoding system used worldwide. So the content inside a &str or String can be in any language:

fn main() {
    let name = "자우림";                                         
    let other_name = String::from("Adrian Fahrenheit Țepeș");   
}

This &str of a Korean rock band’s name is no problem; Korean characters are UTF-8, too.

This String holding a famous vampire’s name is no problem either: Ţ and ș are valid UTF-8.

You can see in String::from("Adrian Fahrenheit Țepeș") that it is easy to make a String from a &str. This second variable is an owned String.

You can even write emojis, thanks to UTF-8:

fn main() {
    let name = "😂";
    println!("My name is actually {}", name);
}

On your computer, that will print My name is actually 😂 unless your command line can’t print it. Then it will show something like My name is actually . But Rust itself has no problem with emojis or any other Unicode, even if your command line can’t display them.

Let’s look at the reason for using a & for strs again to make sure we understand. A str is a dynamically sized type. Dynamically sized means that the size can be different. For example, the two names we saw before (자우림 and Adrian Fahrenheit Țepeș) are not the same size. We can see this with two functions: size_of, which shows the size of a type, and size_of_val, which shows the size of a value pointed to. It looks like this:

fn main() {
    let size_of_string = std::mem::size_of::<String>();      
    let size_of_i8 = std::mem::size_of::<i8>();
    let size_of_f64 = std::mem::size_of::<f64>();
    let size_of_jaurim = std::mem::size_of_val("자우림");     
    let size_of_adrian = std::mem::size_of_val("Adrian Fahrenheit Țepeș");
 
    println!("A String is Sized and always {size_of_string} bytes.");
    println!("An i8 is Sized and always {size_of_i8} bytes.");
    println!("An f64 is always Sized and {size_of_f64} bytes.");
    println!("But a &str is not Sized: '자우림' is {size_of_jaurim} bytes.");
    println!("And 'Adrian Fahrenheit Țepeș' is {size_of_adrian} bytes - not Sized.");
}

std::mem::size_of::<Type>() gives you the size of a type in bytes.

std::mem::size_of_val() gives you the size in bytes of a value.

This prints

A String is Sized and always 24 bytes.
An i8 is Sized and always 1 bytes.
An f64 is always Sized and 8 bytes.
But a &str is not Sized: '자우림' is 9 bytes.
And 'Adrian Fahrenheit Țepeș' is 25 bytes - not Sized.

That is why we need a & because it makes a pointer, and Rust knows the size of the pointer. So, only the pointer goes on the stack. If we wrote str, Rust wouldn’t know what to do because it doesn’t know the size. Actually, you can try it out by telling it to make a str instead of a &str:

fn main() {
    let my_name: str = "My name";
}

Here’s the error:

error[E0308]: mismatched types
 --> src/main.rs:2:24
  |
2 |     let my_name: str = "My name";
  |                  ---   ^^^^^^^^^ expected `str`, found `&str`
  |                  |
  |                  expected due to this
 
error[E0277]: the size for values of type `str` 
cannot be known at compilation time
 --> src/main.rs:2:9
  |
2 |     let my_name: str = "My name";
  |         ^^^^^^^ doesn't have a size known at compile-time
  |
  = help: the trait `Sized` is not implemented for `str`
  = note: all local variables must have a statically known size
  = help: unsized locals are gated as an unstable feature
help: consider borrowing here
  |
2 |     let my_name: &str = "My name";
  |                  +

Not a bad error message! The compiler itself seems to enjoy teaching Rust.

There are many ways to make a string. Here are some:

fn main() {
    let name = "Billybrobby";
    let country = "USA";
    let home = "Korea";
    let together = format!("I am {name} from {country} but I live in {home}.");
}

Now we have a String named together, but we have not printed it yet.

Another way to make a String is called .into(), but it is a bit different because .into() isn’t for making a string; it’s for converting from one type into another type. Some types can easily convert to and from another type using From:: and .into(); if you have From, you also have .into(). From is clearer because you already know the types: you know that String::from("Some str") is a String from a &str. But with .into(), sometimes the compiler doesn’t know:

fn main() {
    let my_string = "Try to make this a String".into();
}

NOTE How does this happen? It’s thanks to something called a blanket trait implementation. We’ll learn about that much later.

Rust doesn’t know what type you want because many types can be made from a &str. It is saying, “I can make a &str into a lot of things, so which one do you want?”

error[E0282]: type annotations needed
 --> src\main.rs:2:9
  |
2 |     let my_string = "Try to make this a String".into();
  |         ^^^^^^^^^ consider giving `my_string` a type

So, you can do this:

fn main() {
    let my_string: String = "Try to make this a String".into();
}

And now you get a String.

Next up are two keywords that let you make global variables. Global variables last forever, so you don’t need to think about ownership for them!

2.3 const and static

There are two other ways to declare values without the keyword let. These two are known as const and static. Another difference is that Rust won’t use type inference for them: you need to write their type. These are for values that don’t change (const means constant). Well, technically, static can change, but we will learn about that later. The two main differences are

For the time being, you can think of them as almost the same. For a global variable, Rust programmers will usually use const, but there are good reasons for the static keyword, too. You’ll know about the key differences between the two by the end of chapter 16.

You write them with ALL CAPITAL LETTERS and usually outside of main so that they can live for the whole program. Two quick examples are

const NUMBER_OF_MONTHS: u32 = 12;
static SEASONS: [&str; 4] = ["Spring", "Summer", "Fall", "Winter"];

Because they are global, you can access them anywhere, and they don’t get dropped. Here’s a quick example. Note that this print_months() function has no input, but no problem—NUMBER_OF_MONTHS can be accessed from anywhere:

const NUMBER_OF_MONTHS: u32 = 12;
 
fn print_months() {                       
    println!("Number of months in the year: {NUMBER_OF_MONTHS}");
}
 
fn main() {
    print_months();
}

This function takes no input!

That was pretty convenient. So, why not just make everything global? One reason is that these types are made at compile time, before the program runs. If you don’t know what a value is during compile time, you can’t make it a const or static. Also, you can’t use the heap during compile time because the program needs to perform a memory allocation (an allocation is like a reservation for heap memory). Don’t worry: you don’t need to allocate memory yourself. Rust takes care of memory allocation for you.

const and static are pretty easy: if the compiler lets you make one, you have it to use anywhere, and you don’t have to worry about ownership. So let’s move on to references because for those you need to understand ownership, and that takes a bit longer to learn.

2.4 More on references

We have learned about references in general, and we know that we use & to create a reference. Let’s look at an example of some code with references:

fn main() {
    let country = String::from("Austria");
    let ref_one = &country;
    let ref_two = &country;
    println!("{}", ref_one);
}

This prints Austria.

Inside the code is the variable country, which is a String and, therefore, owns its data. We then created two references to country. They have the type &String, which is a “reference to a String.” These two variables can look at the data owned by country. We could create 3 references or 100 references to country, and it would be no problem because they are just viewing the data.

But this next code is a problem. Let’s see what happens when we try to return a reference to a String from a function:

fn return_str() -> &String {
    let country = String::from("Austria");
    let country_ref = &country;
    country_ref
}
 
fn main() {
    let country = return_str();
}

Here’s what the compiler says:

error[E0515]: cannot return value referencing local variable country
 --> src/main.rs:4:5
  |
3 |     let country_ref = &country;
  |                       -------- `country` is borrowed here
4 |     country_ref
  |     ^^^^^^^^^^^ returns a value referencing data owned by the current function

The function return_str() creates a String, and then it creates a reference to the String. Then it tries to return the reference. But the String called country only lives inside the function, and then it dies (remember, a variable only lives as long as its code block). Once a variable is gone, the computer will clean up the memory so that it can be used for something else. So after the function returns, country_ref would be referring to memory that is already gone. Definitely not okay! Rust prevents us from making a mistake with memory here.

This is the important part about the “owned” type that we talked about previously. Because you own a String, you can pass it around. But a &String will die if its String dies, and you don’t pass around ownership with it.

2.5 Mutable references

If you want to use a reference to change data, you can use a mutable reference. For a mutable reference, you write &mut instead of &:

fn main() {
    let mut my_number = 8;         
    let num_ref = &mut my_number;
}

Don’t forget to write mut here!

So what are these two types called? my_number is an i32, and num_ref is &mut i32. In speech, you call this a “mutable reference to an i32” or a “ref mut i32.”

Let’s use it to add 10 to my_number. However, you can’t write num_ref += 10 because num_ref is not the i32 value; it is an &i32. There’s nothing to add inside a reference. The value to add is actually inside the i32. To reach the place where the value is, we use *. Using * lets you move from the reference to the value behind the reference. In other words, * is the opposite of &. Also, one * erases one &.

The following code demonstrates these two concepts. It uses * to change the value of a number through a mutable reference and shows that one * equals one &.

fn main() {
    let mut my_number = 8;
    let num_ref = &mut my_number;
    *num_ref += 10;                 
    println!("{}", my_number);
 
    let second_number = 800;
    let triple_reference = &&&second_number;
    println!("Are they equal? {}", second_number == ***triple_reference);
}

Use * to change the i32 value.

This prints

18
Are they equal? true

Because using & is called referencing, using * is called dereferencing.

2.5.1 Rust’s reference rules

Rust has two rules for mutable and immutable references. They are very important but easy to remember because they make sense:

Because mutable references can change the data, you could have problems if you change the data when other references are reading it. A good way to understand is to think of a presentation made with Powerpoint or on Google Docs. Let’s look at some ownership situations through a comparison with real life and determine whether they are okay or not.

2.5.2 Situation 1: Only one mutable reference

Say you are an employee writing a presentation using Google Docs online. You own the data. Now you want your manager to help you. You log in with your account on your manager’s computer and ask the manager to help by making edits. Now, the manager has a mutable reference to your presentation but doesn’t own your computer. The manager can make any changes wanted and then log out after the changes are done. This is fine because nobody else is looking at the presentation.

2.5.3 Situation 2: Only immutable references

Say you are giving the presentation to 100 people. All 100 people can now see your data. They all have an immutable reference to your presentation. This is fine because they can see it, but nobody can change the data. One thousand or 1 million more people can come to the presentation, and it wouldn’t make any difference.

2.5.4 Situation 3: The problem situation

Say you log in on your manager’s computer, as before. The manager now has a mutable reference. Then you give the presentation to 100 people, but the manager hasn’t logged out yet. This is definitely not fine because the manager can still do anything on the computer. Maybe the manager will delete the presentation and start typing an email or even something worse! Now, the 100 people have to watch the manager’s random computer activity instead of the presentation. That’s unexpected behavior and exactly the sort of situation that Rust prevents.

Here is an example of a mutable borrow with an immutable borrow:

fn main() {
    let mut number = 10;
    let number_ref = &number;
    let number_change = &mut number;
    *number_change += 10;
    println!("{}", number_ref);
}

The compiler prints a helpful message to show us the problem:

error[E0502]: cannot borrow `number` as mutable because it is also borrowed as immutable
 --> src\main.rs:4:25
  |
3 |     let number_ref = &number;
  |                      ------- immutable borrow occurs here
4 |     let number_change = &mut number;
  |                         ^^^^^^^^^^^ mutable borrow occurs here
5 |     *number_change += 10;
6 |     println!("{}", number_ref);
  |                    ---------- immutable borrow later used here

Take a close look at the next code sample. In the sample, we create a mutable variable and then a mutable reference. The code changes the value of the variable through the reference. Finally, it creates an immutable reference and prints the value using the immutable reference. That sounds like a mutable borrow together with an immutable borrow, but the code works. Why?

fn main() {
    let mut number = 10;
    let number_change = &mut number;
    *number_change += 10;
    let number_ref = &number;
    println!("{}", number_ref);
}

It prints 20 with no problem. The code works because the compiler is smart enough to understand it. It knows that we used number_change to change number but didn’t use it again, so that is the end of the mutable borrow. No problem! We are not using immutable and mutable references together.

Earlier in Rust’s history, this kind of code actually generated an error, but the compiler is smarter than it used to be. It can understand not just what we type but when and how we use (almost) everything.

2.6 Shadowing again

Remember when we learned in the last chapter that shadowing doesn’t destroy a value but blocks it? We can prove this now that we know how to use references. Take a look at this code and think about what the output will be. Will it be Austria 8 or 8 8?

fn main() {
    let country = String::from("Austria");
    let country_ref = &country;
    let country = 8;
    println!("{country_ref} {country}");
}

The answer is Austria, 8. First, we declare a String called country. Then we create the reference country_ref to this string. Then we shadow country with 8, which is an i32. But the first country was not destroyed, so country_ref still points to "Austria", not 8. Here is the same code with some comments to show how it works:

fn main() {
    let country = String::from("Austria");   
    let country_ref = &country;              
    let country = 8;                         
 
    println!("{country_ref}, {country}");    
}

We have a String called country.

Makes a reference to the String data

Next, we have a variable called country that is an i8. It blocks the original String, but the String is not destroyed.

The reference still points to the String.

References get even more interesting when we pass them into functions because of ownership: functions take ownership, too! The first code example in the next section is a surprise for most people who are learning Rust for the first time. Let’s take a look.

2.7 Giving references to functions

One of the rules of values in Rust is that value can only have one owner. This makes references very useful for functions because you can give a function a quick view of some data without having to pass ownership.

The following code doesn’t work, but it gives us some insight into how ownership works:

fn print_country(country_name: String) {
    println!("{country_name}");
}
 
fn main() {
    let country = String::from("Austria");
    print_country(country);                  
    print_country(country);                  
}

Prints "Austria".

That was fun. Let’s do it again!

It does not work because country ends up destroyed, and the memory gets cleaned up after the first calling of the print_country() function. Here’s how:

This is also called a move because the data moves into the function, and that is where it ends. You will see this in the error output, which calls it a use of moved value. In other words, you tried to use a value that was moved somewhere else. The compiler is helpful enough to show you exactly which line moved the data:

error[E0382]: use of moved value: `country`
 --> src/main.rs:8:19
  |
6 |     let country = String::from("Austria");
  |         ------- move occurs because `country` has type 
  `String`, which does not implement the `Copy` trait
7 |     print_country(country);
  |                   ------- value moved here
8 |     print_country(country);
  |                   ^^^^^^^ value used here after move

This example shows why software written in Rust is fast. A string allocates memory, and if you do a lot of memory allocation, your program might slow down.

NOTE Of course, a few extra allocations won’t slow anything down for the small examples in this book. But sometimes, you need to write software that doesn’t use any extra memory at all. For software like multiplayer games and large data processing, you don’t want to use any extra memory if you don’t have to.

Rust won’t allocate new memory for another string unless you want it to. Instead, it just gives ownership of the same data to something else. In this case, the function becomes the owner of the same data. (Also note the part of the error message that says which does not implement the 'Copy' trait. We’ll learn about this shortly.)

So what do we do? Well, we could make print_country give the String back, but that would be awkward:

fn print_country(country_name: String) -> String {   
    println!("{}", country_name);
    country_name
}
 
fn main() {
    let country = String::from("Austria");
    let country = print_country(country);            
    print_country(country);
}

Now this function doesn’t just print the String; it prints it and returns it ...,

... which means that we have to grab the return value from the function and assign it to a variable again.

Now it prints

Austria
Austria

This way is awkward on both sides: you have to make the function return the value, and you have to declare a variable to hold the value that the function returns. Fortunately, there is a better method—just add &:

fn print_country(country_name: &String) {
    println!("{}", country_name);
}
 
fn main() {
    let country = String::from("Austria");
    print_country(&country);               
    print_country(&country);
}

Note that you have to pass in &country, not country.

Now print_country() is a function that takes a reference to a String: a &String. Thanks to this, the print_country() function can only view the data but never takes ownership.

Now let’s do something similar with a mutable reference. Here is an example of a function that uses a mutable variable:

fn add_hungary(country_name: &mut String) {    
    country_name.push_str("-Hungary");         
    println!("Now it says: {country_name}");
}
 
fn main() {
    let mut country = String::from("Austria");
    add_hungary(&mut country);                 
}

This time we are giving a &mut String instead of a &String.

The push_str() method adds a &str to a String.

Also note here that we need to pass in a &mut country, not just a &country.

This prints Now it says: Austria-Hungary.

So, to conclude,

Pay very close attention to this next example. It looks like a mutable reference, but it is different. There is no &, so it’s not a reference at all:

fn main() {
    let country = String::from("Austria");                
    adds_hungary(country);
}
 
fn adds_hungary(mut string_to_add_hungary_to: String) {   
    string_to_add_hungary_to.push_str("-Hungary");
    println!("{}", string_to_add_hungary_to);
}

The variable country is not mutable, but we are going to print Austria-Hungary. How?

Here’s how: adds_hungary takes the String and declares it mutable!

The output is Austria-Hungary, but that’s not the interesting part.

How is this possible? mut string_to_add_hungary_to is not a reference: adds_hungary owns country now. Remember, it takes a String and not a &String. The moment you call adds_hungary, this function becomes the full owner of the data. The variable string_to_add_hungary_to doesn’t need to care about the variable country at all because its data has moved and country is now gone. So adds_hungary can take country as mutable, and it is perfectly safe to do so—nobody else owns it.

Remember our employee and manager situation? In this situation, it is like you just quit your job and gave your whole computer to the manager. You’re gone. You won’t ever touch it again, so the manager can do anything at all to it.

Even more interesting, if you declare country a mutable variable on line 2, the compiler will give you a small warning. Why do you think that is?

fn main() {
    let mut country = String::from("Austria");     
    adds_hungary(country);
}
 
fn adds_hungary(mut string_to_add_hungary_to: String) {
    string_to_add_hungary_to.push_str("-Hungary");
    println!("{}", string_to_add_hungary_to);
}

Here we have declared country as mut.

Let’s look at the warning:

warning: variable does not need to be mutable
 --> src/main.rs:2:9
  |
2 |     let mut country = String::from("Austria");
  |         ----^^^^^^^
  |         |
  |         help: remove this `mut`
  |
  = note: `#[warn(unused_mut)]` on by default

This makes sense because on line 2 we have an owner called country that owns this mutable String. But it doesn’t mutate it! It simply passes it into the adds_hungary function. There was no need to make it mutable. But the adds_hungary function takes ownership and would like to mutate it, so it declares it as a mut string_to_add_ hungary_to. (It could have called it mut country, but with a different name, we can make it clear that ownership has completely passed to the function.)

To take the employee and manager comparison again, it is as if you started at a new job, got assigned a computer, and then quit and gave it to your manager without even booting it up. There was no need to have mutable access in the first place because you never even touched it.

Also note the position: it’s mut country: String and not country: mut String. This is the same order as when you use let like in let mut country: String.

2.8 Copy types

Rust’s simplest types are known as Copy types. They are all on the stack, and the compiler knows their size. That means that they are very easy to copy, so the compiler always copies their data when you send these types to a function. Copy types are so small and easy that there is no reason not to. In other words, you don’t need to worry about ownership for these types.

We saw that in the previous section, too: the compiler said that the data for String moved because a String isn’t a Copy type. If it was a Copy type, the data would be copied, not moved. Sometimes you’ll see this difference expressed as move semantics and copy semantics. You also see the word trivial to talk about Copy types a lot, such as “It’s trivial to copy them.” That means “it’s so easy to copy them that there is no reason not to copy them.” Copy types include integers, floats, booleans (true and false), char, and others.

How do you know if a type “implements” (can use) copy? You can check the documentation. For example, the documentation for char can be found at https://doc.rust-lang.org/std/primitive.char.html. On the left in the documentation, you can see Trait Implementations. There you can see, for example, Copy, Debug, and Display. With that, you know that a char

Let’s look at a code sample similar to the previous one, except that it involves a function that takes an i32 (a Copy type) instead of a String. You don’t need to think about ownership anymore in this case because the i32 simply gets copied every time it passes into the print_number() function:

fn prints_number(number: i32) {    
    println!("{}", number);
}
 
fn main() {
    let my_number = 8;
    prints_number(my_number);      
    prints_number(my_number);      
}

There is no -> so the function does not return anything. If number was not a Copy type, the function would take it, and we couldn’t use it again.

prints_number gets a copy of my_number.

And again here. It’s not a problem because my_number is a Copy type!

But if you look at the documentation for String (https://doc.rust-lang.org/std/string/struct.String.html), it is not a Copy type.

On the left in Trait Implementations, you can look in alphabetical order (A, B, C, etc.); there is no Copy in C. But there is one called Clone. Clone is similar to Copy but usually needs more memory. Also, you have to call it with .clone()—it won’t clone just by itself in the same way that Copy types copy themselves all on their own.

Let’s get back to the previous example with the prints_country() function. Remember that you can’t pass the same String in twice because the function takes ownership:

fn prints_country(country_name: String) {
    println!("{country_name}");
}
 
fn main() {
    let country = String::from("Kiribati");
    prints_country(country);
    prints_country(country);
}

But now we understand the message:

error[E0382]: use of moved value: `country`
 --> src\main.rs:4:20
  |
2 |     let country = String::from("Kiribati");
  |         ------- move occurs because `country` 
  has type `std::string::String`, which does not implement the `Copy` trait
3 |     prints_country(country);
  |                    ------- value moved here
4 |     prints_country(country);
  |                    ^^^^^^^ value used here after move

The important part is String, which does not implement the `Copy` trait.

But what if this was someone else’s code, and we couldn’t change the prints_country() function to take a &String instead of a String? Or what if we wanted to take a String by value for some reason? Well, in the documentation, we saw that String implements the Clone trait. So we can add .clone() to our code. This creates a clone, and we send the clone to the function. Now country is still alive, so we can use it:

fn prints_country(country_name: String) {
    println!("{}", country_name);
}
 
fn main() {
    let country = String::from("Kiribati");
    prints_country(country.clone());         
    prints_country(country);
}

Makes a clone and gives it to the function. Only the clone goes in, and the country variable is still alive.

Of course, if the String is very large, .clone() can use a lot of memory. One String could be a whole book in length, and every time we call .clone(), it will copy the contents of the book. So using & for a reference is faster, if you can. For example, the following code pushes a &str onto a String and then makes a clone every time it gets used in a function:

fn get_length(input: String) {            
    println!("It's {} words long.", 
    input.split_whitespace().count());    
}
 
fn main() {
    let mut my_string = String::new();
    for _ in 0..50 {
        my_string.push_str("Here are some more words ");
        get_length(my_string.clone());
    }
}

This function takes ownership of a String.

Here we split to count the number of words. (Whitespace means the space between words.)

It prints

It's 5 words long.
It's 10 words long.
...
It's 250 words long.

That’s 50 clones. Here it is using a reference instead, which is better:

fn get_length(input: &String) {
    println!("It's {} words long.", input.split_whitespace().count());
}
 
fn main() {
    let mut my_string = String::new();
    for _ in 0..50 {
        my_string.push_str("Here are some more words ");
        get_length(&my_string);
    }
}

Instead of 50 clones, it’s zero.

Here’s a good rule of thumb with references and functions: if you can use an immutable reference, go with that. You won’t have to worry about a function taking ownership of some data: the function will simply take a look at it and be done. For functions, if you don’t need to transfer ownership, a reference is always easiest!

The final memory-related subject in this chapter is pretty short: variables that have a name but no values.

2.9 Variables without values

A variable without a value is called an uninitialized variable. Uninitialized means “hasn’t started yet.” They are simple: just write let and then the variable name and (if necessary) the type:

fn main() {
    let my_variable: i32;
}

You can’t use it yet, and Rust won’t compile if you try to use a value that isn’t initialized. But sometimes they can be useful. Some examples are when

Here’s a simple example:

fn main() {
    let my_number;
    {                                    
        let calculation_result = {       
            57                           
        };
        my_number = calculation_result;  
        println!("{my_number}");
    }
}

Pretend we need to have this code block. We are writing some complex logic.

Pretend there is code here, too.

Lots of code and then the result

And, finally, gives my_number a value

This prints 57.

You can see that my_number was declared in the main() function, so it lives until the end of the function. It gets its value from inside a different block, but that value lives as long as my_number because my_number holds the value.

Also note that my_number is not mut and doesn’t have to be. We didn’t give it a value until we gave it 57, so it never mutated its value. In the end, my_number is just a number that finally gets initialized with the value 57.

2.10 More about printing

We learned the basics of printing in the last chapter, but there is quite a bit more to know. You can print in a lot of ways in Rust: complex formatting, printing as bytes, displaying pointer addresses (the part in memory where the pointer is), and a lot more. Let’s take a look at all of that now.

Adding \n will make a new line, and \t will make a tab:

fn main() {                                                
    print!("\t Start with a tab\nand move to a new line");
}

This is print!, not println!

This prints

         Start with a tab
and move to a new line

Inside a single "" you can write over multiple lines, but be careful with the spacing:

fn main() {                   
    println!("Inside quotes
you can write over
many lines
and it will print just fine.");
 
    println!("If you forget to write
    on the left side, the spaces
    will be added when you print.");
}

After the first line, you have to start on the far left. If you write directly under println!, it will add the spaces.

This prints

Inside quotes
you can write over
many lines
and it will print just fine.
If you forget to write
    on the left side, the spaces
    will be added when you print.

If you want to print characters like \n, you can add an extra \ (a backslash). This is what is known as an “escape”:

fn main() {
    println!("Here are two escape characters: \\n and \\t");
}

This prints

Here are two escape characters: \n and \t

Sometimes you end up using too many escape characters and just want Rust to print a string as you see it. To do this, you can add r# to the beginning and # to the end. The r here stands for raw:

fn main() {
    println!("He said, \"You can find the file at
    c:\\files\\my_documents\\file.txt.\" Then I found the file.");   
    println!(r#"He said, "You can find the file at c:\files\my_documents\file.txt." Then I found the file."#);       
}

We had to use \ eight times here—kind of annoying.

Much better!

The output is exactly the same:

He said, "You can find the file at c:\files\my_documents\file.txt." 
Then I found the file.
He said, "You can find the file at c:\files\my_documents\file.txt." 
Then I found the file.

But the code for the second println! is easier to read.

But what if # marks the end of the string and you need to print text with a #" inside? In that case, you can start with r## and end with ##. You can keep adding # to the beginning and end if you have longer instances of the # symbol in your text.

This is best understood with a few examples:

fn main() {
 
    let my_string = "'Ice to see you,' he said.";
    let quote_string = r#""Ice to see you," he said."#;
    let hashtag_string = r##"The hashtag "#IceToSeeYou" had become 
    very popular."##;
    let many_hashtags = r####""You don't have to type "###" to 
    use a hashtag. You can just use #."####;
 
    println!("{}\n{}\n{}\n{}\n", my_string, quote_string, 
    hashtag_string, many_hashtags);
}

The output of these four examples is

'Ice to see you,' he said.
"Ice to see you," he said.
The hashtag "#IceToSeeYou" had become very popular.
"You don't have to type "###" to use a hashtag. You can just use #.

If you want to print the bytes of a &str or a char, you can write b before the string. This works for all ASCII characters (https://theasciicode.com.ar/).

So when you add a b to print as follows,

fn main() {
    println!("{:?}", b"This will look like numbers");
}

you will get an output that shows all the bytes:

[84, 104, 105, 115, 32, 119, 105, 108, 108, 32, 108, 111, 111, 107, 32, 108, 105, 107, 101, 32, 110, 117, 109, 98, 101, 114, 115]

You can also put b and r together if you need to:

fn main() {
    println!("{:?}", br##"I like to write "#"."##);
}

That will print [73, 32, 108, 105, 107, 101, 32, 116, 111, 32, 119, 114, 105, 116, 101, 32, 34, 35, 34, 46].

There is also a Unicode escape that lets you print any Unicode character inside a string: \u{}. A hexadecimal number goes inside the {} to print it. Here is a short example of how to get the Unicode number as a u32, which you can then use with \u to print it out again:

fn main() {
    println!("{:X}", '' as u32);                      
    println!("{:X}", 'H' as u32);
    println!("{:X}", '' as u32);
    println!("{:X}", '' as u32);
 
    println!("\u{D589}, \u{48}, \u{5C45}, \u{3044}");   
}

Casts char as u32 to get the hexadecimal value

Tries printing them with unicode escape \u

We know that println! can print with {} for Display and {:?} for Debug, plus {:#?} for pretty printing. But there are many other ways to print.

For example, if you have a reference, you can use {:p} to print the pointer address. Pointer address means the location in your computer’s memory:

fn main() {
    let number = 9;
    let number_ref = &number;
    println!("{:p}", number_ref);
}

This prints an address, like 0xe2bc0ffcfc. It might be different every time, depending on how and where your computer stores it. Or you can print binary, hexadecimal, and octal:

fn main() {
    let number = 555;
    println!("Binary: {:b}, hexadecimal: {:x}, octal: {:o}", number, number, number);
}

This prints Binary: 1000101011, hexadecimal: 22b, octal: 1053.

You can also add numbers inside {} to change the order of what gets printed. The first variable following the string will be in index 0, the next in index 1, and so on:

fn main() {
    let father_name = "Vlad";
    let son_name = "Adrian Fahrenheit";
    let family_name = "Țepeș";
    println!("This is {1} {2}, son of {0} {2}.", 
    father_name, son_name, family_name);
}

Here, father_name is in position 0, son_name is in position 1, and family_name is in position 2. So it prints This is Adrian Fahrenheit Țepeș, son of Vlad Țepeș.

You can also use a name instead of an index value to do the same thing. In this case, you have to use the = sign to indicate which name applies to which value:

fn main() {
    println!(
        "{city1} is in {country} and {city2} 
    is also in {country},
but {city3} is not in {country}.",
        city1 = "Seoul",
        city2 = "Busan",
        city3 = "Tokyo",
        country = "Korea"
    );
}

That example prints

Seoul is in Korea and Busan is also in Korea,
but Tokyo is not in Korea.

Very complex printing is also possible in Rust if you want to use it. Complex printing in Rust is based on this format:

{variable:padding alignment minimum.maximum}

Let’s look at this one step at a time to understand it:

We use this format every time we print, actually. If you type println!("{my_ type:?}");, you are choosing the following:

Let’s look at some complex printing examples. If you want to write a with five Korean characters on the left and five characters on the right, you would write this:

fn main() {
    let letter = "a";
    println!("{:^11}", letter);
}

This prints ㅎㅎㅎㅎㅎaㅎㅎㅎㅎㅎ. Let’s look at 1 to 5 again to understand how the compiler reads it:

Here is an example of many types of formatting:

fn main() {
    let title = "TODAY'S NEWS";
    println!("{:-^30}", title);                                  
    let bar = "|";
    println!("{: <15}{: >15}", bar, bar);                        
    let a = "SEOUL";
    let b = "TOKYO";
    println!("{city1:-<15}{city2:->15}", city1 = a, city2 = b);  
}

No variable name; pad with -; ^ to put in center; 30 characters long

No variable name; pad with space; 15-character minimum length (one to the left, one to the right)

Variable names city1 and city2; pad with -; 15 character minimum length (one to the left, one to the right)

It prints

---------TODAY'S NEWS---------
|                            |
SEOUL--------------------TOKYO

This chapter had a lot of concepts that are unique to Rust regarding memory and ownership. Let’s think of one more comparison to make sure we understand it before we finish the chapter.

Ownership is sort of like how you own your computer. You have

Finally, there are Copy types: they are “trivial” to copy. Think of them as cheap office pens, paperclips, and stickies. If your coworker needs a paperclip, do you care about ownership? No, you just hand it over and forget about it because it’s such a trivial item.

Now that you understand ownership, we are going to move on to some more interesting types in Rust. So far, we’ve only looked at the most primitive types and String, but there’s a lot more out there. In the next chapter we’ll start to look at Rust’s collection types.

Summary