Understanding References and Borrowing in Rust

Understanding References and Borrowing in Rust

References and Borrowing: This is one of Rust’s most distinct and compelling features, with which Rust developers should become quite acquainted. Ownership is how Rust achieves its largest goal, memory safety. Ownership - the key concept, Borrowing - which you’re reading now, Lifetimes - an advanced concept of borrowing. You’ll need all three to fully understand the ownership system.

This is one of Rust’s most distinct and compelling features, with which Rust developers should become quite acquainted. Ownership is how Rust achieves its largest goal, memory safety. There are a few distinct concepts, each with its own chapter:

  • ownership, the key concept
  • borrowing, which you’re reading now
  • lifetimes, an advanced concept of borrowing

These three chapters are related, and in order. You’ll need all three to fully understand the ownership system.

Meta

Before we get to the details, two important notes about the ownership system.

Rust has a focus on safety and speed. It accomplishes these goals through many ‘zero-cost abstractions’, which means that in Rust, abstractions cost as little as possible in order to make them work. The ownership system is a prime example of a zero-cost abstraction. All of the analysis we’ll talk about in this guide is done at compile time. You do not pay any run-time cost for any of these features.

However, this system does have a certain cost: learning curve. Many new users to Rust experience something we like to call ‘fighting with the borrow checker’, where the Rust compiler refuses to compile a program that the author thinks is valid. This often happens because the programmer’s mental model of how ownership should work doesn’t match the actual rules that Rust implements. You probably will experience similar things at first. There is good news, however: more experienced Rust developers report that once they work with the rules of the ownership system for a period of time, they fight the borrow checker less and less.

With that in mind, let’s learn about borrowing.

Borrowing

At the end of the ownership section, we had a nasty function that looked like this:


# #![allow(unused_variables)]
#fn main() {
fn foo(v1: Vec<i32>, v2: Vec<i32>) -> (Vec<i32>, Vec<i32>, i32) {
    // Do stuff with `v1` and `v2`.

    // Hand back ownership, and the result of our function.
    (v1, v2, 42)
}

let v1 = vec![1, 2, 3];
let v2 = vec![1, 2, 3];

let (v1, v2, answer) = foo(v1, v2);
#}

This is not idiomatic Rust, however, as it doesn’t take advantage of borrowing. Here’s the first step:


# #![allow(unused_variables)]
#fn main() {
fn foo(v1: &Vec<i32>, v2: &Vec<i32>) -> i32 {
    // Do stuff with `v1` and `v2`.

    // Return the answer.
    42
}

let v1 = vec![1, 2, 3];
let v2 = vec![1, 2, 3];

let answer = foo(&v1, &v2);

// We can use `v1` and `v2` here!
#}

A more concrete example:

fn main() {
    // Don't worry if you don't understand how `fold` works, the point here is that an immutable reference is borrowed.
    fn sum_vec(v: &Vec<i32>) -> i32 {
        v.iter().fold(0, |a, &b| a + b)
    }
    // Borrow two vectors and sum them.
    // This kind of borrowing does not allow mutation through the borrowed reference.
    fn foo(v1: &Vec<i32>, v2: &Vec<i32>) -> i32 {
        // Do stuff with `v1` and `v2`.
        let s1 = sum_vec(v1);
        let s2 = sum_vec(v2);
        // Return the answer.
        s1 + s2
    }

    let v1 = vec![1, 2, 3];
    let v2 = vec![4, 5, 6];

    let answer = foo(&v1, &v2);
    println!("{}", answer);
}

Instead of taking Vec<i32>s as our arguments, we take a reference: &Vec<i32>. And instead of passing v1 and v2 directly, we pass &v1 and &v2. We call the &T type a ‘reference’, and rather than owning the resource, it borrows ownership. A binding that borrows something does not deallocate the resource when it goes out of scope. This means that after the call to foo(), we can use our original bindings again.

References are immutable, like bindings. This means that inside of foo(), the vectors can’t be changed at all:

fn foo(v: &Vec<i32>) {
     v.push(5);
}

let v = vec![];

foo(&v);

will give us this error:

error: cannot borrow immutable borrowed content `*v` as mutable
v.push(5);
^

Pushing a value mutates the vector, and so we aren’t allowed to do it.

&mut references

There’s a second kind of reference: &mut T. A ‘mutable reference’ allows you to mutate the resource you’re borrowing. For example:


# #![allow(unused_variables)]
#fn main() {
let mut x = 5;
{
    let y = &mut x;
    *y += 1;
}
println!("{}", x);
#}

This will print 6. We make y a mutable reference to x, then add one to the thing y points at. You’ll notice that x had to be marked mut as well. If it wasn’t, we couldn’t take a mutable borrow to an immutable value.

You'll also notice we added an asterisk (*) in front of y, making it *y, this is because y is a &mut reference. You'll need to use asterisks to access the contents of a reference as well.

Otherwise, &mut references are like references. There is a large difference between the two, and how they interact, though. You can tell something is fishy in the above example, because we need that extra scope, with the { and }. If we remove them, we get an error:

error: cannot borrow `x` as immutable because it is also borrowed as mutable
    println!("{}", x);
                   ^
note: previous borrow of `x` occurs here; the mutable borrow prevents
subsequent moves, borrows, or modification of `x` until the borrow ends
        let y = &mut x;
                     ^
note: previous borrow ends here
fn main() {

}
^

As it turns out, there are rules.

The Rules

Here are the rules for borrowing in Rust:

First, any borrow must last for a scope no greater than that of the owner. Second, you may have one or the other of these two kinds of borrows, but not both at the same time:

  • one or more references (&T) to a resource,
  • exactly one mutable reference (&mut T).

You may notice that this is very similar to, though not exactly the same as, the definition of a data race:

There is a ‘data race’ when two or more pointers access the same memory location at the same time, where at least one of them is writing, and the operations are not synchronized.

With references, you may have as many as you’d like, since none of them are writing. However, as we can only have one &mut at a time, it is impossible to have a data race. This is how Rust prevents data races at compile time: we’ll get errors if we break the rules.

With this in mind, let’s consider our example again.

Thinking in scopes

Here’s the code:

fn main() {
    let mut x = 5;
    let y = &mut x;

    *y += 1;

    println!("{}", x);
}

This code gives us this error:

error: cannot borrow `x` as immutable because it is also borrowed as mutable
    println!("{}", x);
                   ^

This is because we’ve violated the rules: we have a &mut T pointing to x, and so we aren’t allowed to create any &Ts. It's one or the other. The note hints at how to think about this problem:

note: previous borrow ends here
fn main() {

}
^

In other words, the mutable borrow is held through the rest of our example. What we want is for the mutable borrow by y to end so that the resource can be returned to the owner, x. x can then provide an immutable borrow to println!. In Rust, borrowing is tied to the scope that the borrow is valid for. And our scopes look like this:

fn main() {
    let mut x = 5;

    let y = &mut x;    // -+ &mut borrow of `x` starts here.
                       //  |
    *y += 1;           //  |
                       //  |
    println!("{}", x); // -+ - Try to borrow `x` here.
}                      // -+ &mut borrow of `x` ends here.

The scopes conflict: we can’t make an &x while y is in scope.

So when we add the curly braces:


# #![allow(unused_variables)]
#fn main() {
let mut x = 5;

{
    let y = &mut x; // -+ &mut borrow starts here.
    *y += 1;        //  |
}                   // -+ ... and ends here.

println!("{}", x);  // <- Try to borrow `x` here.
#}

There’s no problem. Our mutable borrow goes out of scope before we create an immutable one. So scope is the key to seeing how long a borrow lasts for.

Issues borrowing prevents

Why have these restrictive rules? Well, as we noted, these rules prevent data races. What kinds of issues do data races cause? Here are a few.

Iterator invalidation

One example is ‘iterator invalidation’, which happens when you try to mutate a collection that you’re iterating over. Rust’s borrow checker prevents this from happening:


# #![allow(unused_variables)]
#fn main() {
let mut v = vec![1, 2, 3];

for i in &v {
    println!("{}", i);
}
#}

This prints out one through three. As we iterate through the vector, we’re only given references to the elements. And v is itself borrowed as immutable, which means we can’t change it while we’re iterating:

let mut v = vec![1, 2, 3];

for i in &v {
    println!("{}", i);
    v.push(34);
}

Here’s the error:

error: cannot borrow `v` as mutable because it is also borrowed as immutable
    v.push(34);
    ^
note: previous borrow of `v` occurs here; the immutable borrow prevents
subsequent moves or mutable borrows of `v` until the borrow ends
for i in &v {
          ^
note: previous borrow ends here
for i in &v {
    println!(“{}”, i);
    v.push(34);
}
^

We can’t modify v because it’s borrowed by the loop.

Use after free

References must not live longer than the resource they refer to. Rust will check the scopes of your references to ensure that this is true.

If Rust didn’t check this property, we could accidentally use a reference which was invalid. For example:

let y: &i32;
{
    let x = 5;
    y = &x;
}

println!("{}", y);

We get this error:

error: `x` does not live long enough
    y = &x;
         ^
note: reference must be valid for the block suffix following statement 0 at
2:16...
let y: &i32;
{
    let x = 5;
    y = &x;
}

note: ...but borrowed value is only valid for the block suffix following
statement 0 at 4:18
    let x = 5;
    y = &x;
}

In other words, y is only valid for the scope where x exists. As soon as x goes away, it becomes invalid to refer to it. As such, the error says that the borrow ‘doesn’t live long enough’ because it’s not valid for the right amount of time.

The same problem occurs when the reference is declared before the variable it refers to. This is because resources within the same scope are freed in the opposite order they were declared:

let y: &i32;
let x = 5;
y = &x;

println!("{}", y);

We get this error:

error: `x` does not live long enough
y = &x;
     ^
note: reference must be valid for the block suffix following statement 0 at
2:16...
    let y: &i32;
    let x = 5;
    y = &x;

    println!("{}", y);
}

note: ...but borrowed value is only valid for the block suffix following
statement 1 at 3:14
    let x = 5;
    y = &x;

    println!("{}", y);
}

In the above example, y is declared before x, meaning that y lives longer than x, which is not allowed.

Angular 9 Tutorial: Learn to Build a CRUD Angular App Quickly

What's new in Bootstrap 5 and when Bootstrap 5 release date?

What’s new in HTML6

How to Build Progressive Web Apps (PWA) using Angular 9

What is new features in Javascript ES2020 ECMAScript 2020

Rust & WebAssembly para JavaScripters

Rust & WebAssembly para JavaScripters

A lo largo de la charla descubriremos las características más destacables de Rust, sus similitudes y diferencias con JavaScript y veremos qué aporta Rust al futuro de la Web gracias a WebAssembly. Rust es un lenguaje tipado, rápido y seguro, que ha sido diseñado por Mozilla como lenguaje de sistemas, aunque en los últimos tiempos ha ganado mucha popularidad en el terreno del desarrollo Web gracias a WebAssembly, su amplio ecosistema y gran comunidad

Rust es un lenguaje tipado, rápido y seguro, que ha sido diseñado por Mozilla como lenguaje de sistemas, aunque en los últimos tiempos ha ganado mucha popularidad en el terreno del desarrollo Web gracias a WebAssembly, su amplio ecosistema y gran comunidad. A lo largo de la charla descubriremos las características más destacables de Rust, sus similitudes y diferencias con JavaScript y veremos qué aporta Rust al futuro de la Web gracias a WebAssembly.

Rust vs. Go: Should I Rust, or Should I Go

Rust vs. Go: Should I Rust, or Should I Go

Well both Rust and Go provide amazing performance. Should you write you’re next big thing with Rust or with Go? Go is fast and powerful, but it avoids bogging the developer down, focusing instead on simplicity and uniformity. Rust. If on the other hand, wringing out every last ounce of performance is a necessity, then Rust should be your choice. Rust is more of a competitor to C++ than it is with Go.

Should I stay, or should I go?” Great song by the band The Clash. I’m listening to it, right now, while I’m writing this article. The song debuted back in 1982, a long time ago. Back then, I was just a kid exploring a new hobby — programming my Atari 2600. The first video game I ever wrote was written using 6502 Assembly for that console. The compiler for it cost about $65, if I recall, which at the time equated to mowing ~13 or so lawns.

The game was simple: using the joystick, maneuver your spaceship through a randomly generated scrolling cave. The cave walls were sinusoidal, scrolling vertically on the left and right sides of the screen, and you had to make sure your craft didn’t crash into them. I know, I know: Not that sophisticated. But I was only ten or eleven years old at the time.

Despite the “power” of the processor, computing sine values at run-time was simply too much for it. So, using my handy Texas Instruments calculator, I pre-calculated a bunch of the sine values, carefully writing them down on paper, and then entering them in as constants for the game. This greatly enhanced the performance of the game, and made it usable.

So what’s my point? What’s any of this got to do with Rust or Go?

Today’s languages are far more advanced than 6502 Assembly, which make it easier to write complex programs. It took a lot of my time to write that game, and I could do it much faster today, with less code than I did back then. But which language today provides that magic combination of simplicity and power?

Well both Rust and Go provide amazing performance. They both compile to machine code, the Holy Grail of performance. And with today’s processing power, developers can do amazing things with either of these languages. So the question is: Should you write you’re next big thing with Rust or with Go?

With a quick search, you can easily find several articles that go into detail about the differences between the two languages. But the focus of this article is the bang for the buck, that magic combination of performance per line of code.

To put it another way, where is that sweet spot of simple code and top-end performance? And in this case, is it Rust, or is it Go?
There really isn’t any argument: Rust is faster than Go. In the benchmarks above, Rust was faster, and in some cases, an order of magnitude faster.

But before you run off choosing to write everything in Rust, consider that Go wasn’t that far behind it in many of those benchmarks, and it’s still much faster than the likes of Java, C#, JavaScript, Python and so on. So in other words, it’s almost a wash between Rust and Go on the axis of performance. Now, if what you’re building needs to wring out every last ounce of performance, then by all means, choose Rust. But if what you need is top-of-the-line performance, then you’ll be ahead of the game choosing either of these two languages.

So then we’re down to the complexity of the code. This is where things can be muddy since this can be more subjective than performance benchmarks. Let’s look at a simple exercise: building a small web server that prints out “Hello World” when it receives an HTTP request. To do this in Rust, it looks something like this:

use std::net::{TcpStream, TcpListener};
use std::io::{Read, Write};
use std::thread;


fn handle_read(mut stream: &TcpStream) {
    let mut buf = [0u8; 4096];
    match stream.read(&mut buf) {
        Ok(_) => {
            let req_str = String::from_utf8_lossy(&buf);
            println!("{}", req_str);
            },
        Err(e) => println!("Unable to read stream: {}", e),
    }
}

fn handle_write(mut stream: TcpStream) {
    let response = b"HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=UTF-8\r\n\r\n<html><body>Hello world</body></html>\r\n";
    match stream.write(response) {
        Ok(n) => println!("Response sent: {} bytes", n),
        Err(e) => println!("Failed sending response: {}", e),
    }
}

fn handle_client(stream: TcpStream) {
    handle_read(&stream);
    handle_write(stream);
}

fn main() {
    let port = "8080";
    let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
    println!("Listening for connections on port {}", port);

    for stream in listener.incoming() {
        match stream {
            Ok(stream) => {
                thread::spawn(|| {
                    handle_client(stream)
                });
            }
            Err(e) => {
                println!("Unable to connect: {}", e);
            }
        }
    }
}

Something pretty similar in Go looks like this:

package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
)

type handler struct{}

func (theHandler *handler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
	log.Printf("Received request: %s\n", request.URL)
	log.Printf("%v\n", request)
	io.WriteString(writer, "Hello world!")
}

const port = "8080"

func main() {
	server := http.Server{
		Addr:    fmt.Sprintf(":%s", port),
		Handler: &handler{},
	}

	server.ListenAndServe()
}

Now, they are not 100% exactly the same, but they are close enough. The difference between them is ~20 lines of code. Rust definitely forces the developer to consider more, and thus write more code than Go.

Another example: Consider one of the more difficult aspects of software development: multi-threading. When tackling something like this, as you undoubtedly would when building an HTTP server, there’s a lot to think about:

  • You need to ensure everything you design is thread safe (locks)
  • You need to handle communication between threads (channels)
  • You have to design with concurrency and parallelism in mind (threads and routines)

Both Rust and Go handle these hurdles really efficiently, but Go requires less effort. With Rust, you have way more options, and thus more power, when spawning threads. Just look at some of the documentation on this. Here’s just one way to spawn a thread in Rust:

use std::thread;

let handler = thread::spawn(|| {
    // thread code
});

handler.join().unwrap();

On the other hand, here’s how to create something similar using Go:

go someFunction(args)

Another crucial part of writing code is handling errors. Here I think Rust and Go are quite similar. Rust enables the developer to handle errors cases through the use of the enum return types: Option<T>and Result<T, E>. The Option<T> will return either None or Some(T) whereas Result<T, E> will return either Ok(T) or Err(T). Given that most of Rust’s own libraries, as well as other third-party libraries, return one of these types, the developer will usually have to handle the case where nothing is returned, or where an error is returned.

Here’s a simple example of the Result type being returned by a function in Rust:

fn foo_divide(a: f32, b: f32) -> Result<f32, &'static str> {
    if b == 0.0 {
        Err("divide by zero error!")
    } else {
        Ok(a / b)
    }
}fn main() {
    match foo_divide(5.0, 4.0) {
        Err(err) => println!("{}", err),
        Ok(result) => println!("5 / 4 = {}", result),
    }
}

Notice that the Err case must be handled within the match statement.

Go, on the other hand, leaves this more up to the developer, since errors can be ignored using the _. However, idiomatic Go strongly recommends returning an error, especially since functions in Go can return multiple values. Therefore, it’s easy to have functions return their intended value along with an error, if there is one.

Here is the corresponding example from above done in Go:

func fooDivide(a float32, b float32) (float32, error) {
    if b == 0 {
        return 0, errors.New("divide by zero error!")
    }    return a / b, nil
}func main() {
    result, err := fooDivide(5, 4)
    if err != nil {
       log.Printf("an error occurred: %v", err)
    } else {
       log.Printf("The answer is: 5 / 4 = %f", result)
    }
}

Notice that this line:

result, err := fooDivide(5, 4)

could have been written as

result, _ := fooDivide(5, 4)

In the latter case, the error returned would have been ignored.

Honestly, they’re both pretty similar, except for Rust forcing error checking. Otherwise, there’s little difference, and it’s difficult to find an advantage one has over the other. To my eyes, this is a draw.

I could keep going, digging deeper into other language differences. But the bottom line, from threads, to channels, to generics, Rust provides the developer with more options. In this respect, Rust is closer to C++ than Go. Does this make Rust inherently more complex?

I think so, yes.

So here are my recommendations:

  • Either. If you’re building a web service that handles high load, that you want to be able to scale both vertically and horizontally, either language will suit you perfectly.
  • Go. But if you want to write it faster, perhaps because you have many different services to write, or you have a large team of developers, then Go is your language of choice. Go gives you concurrency as a first-class citizen, and does not tolerate unsafe memory access (neither does Rust), but without forcing you to manage every last detail. Go is fast and powerful, but it avoids bogging the developer down, focusing instead on simplicity and uniformity.
  • Rust. If on the other hand, wringing out every last ounce of performance is a necessity, then Rust should be your choice. Rust is more of a competitor to C++ than it is with Go. Having battled with C++, Rust feels just as powerful but with many happy improvements. Rust empowers developers to have control over every last detail of how their threads behave with the rest of the system, how errors should be handled, and even the lifetime of their variables!
  • Rust. Rust was designed to interoperate with C. Go can as well, but gives up a lot to achieve this goal, and it’s not really its focus.
  • Go. If readability is a requirement, go with Go. It’s far too easy to make your code hard for others to grok with Rust.

I hope you enjoyed reading this!

The Rust Programming Language - Understanding If in Rust

The Rust Programming Language - Understanding If in Rust

The Rust Programming Language - Understanding If in Rust. Rust’s take on if is not particularly complex, but it’s much more like the if you’ll find in a dynamically typed language than in a more traditional systems language. if is a specific form of a more general concept, the ‘branch’, whose name comes from a branch in a tree: a decision point, where depending on a choice, multiple paths can be taken.

Rust’s take on if is not particularly complex, but it’s much more like the if you’ll find in a dynamically typed language than in a more traditional systems language. So let’s talk about it, to make sure you grasp the nuances.

if is a specific form of a more general concept, the ‘branch’, whose name comes from a branch in a tree: a decision point, where depending on a choice, multiple paths can be taken.

In the case of if, there is one choice that leads down two paths:


# #![allow(unused_variables)]
#fn main() {
let x = 5;

if x == 5 {
    println!("x is five!");
}
#}

If we changed the value of x to something else, this line would not print. More specifically, if the expression after the if evaluates to true, then the block is executed. If it’s false, then it is not.

If you want something to happen in the false case, use an else:


# #![allow(unused_variables)]
#fn main() {
let x = 5;

if x == 5 {
    println!("x is five!");
} else {
    println!("x is not five :(");
}
#}

If there is more than one case, use an else if:


# #![allow(unused_variables)]
#fn main() {
let x = 5;

if x == 5 {
    println!("x is five!");
} else if x == 6 {
    println!("x is six!");
} else {
    println!("x is not five or six :(");
}
#}

This is all pretty standard. However, you can also do this:


# #![allow(unused_variables)]
#fn main() {
let x = 5;

let y = if x == 5 {
    10
} else {
    15
}; // y: i32
#}

Which we can (and probably should) write like this:


# #![allow(unused_variables)]
#fn main() {
let x = 5;

let y = if x == 5 { 10 } else { 15 }; // y: i32
#}

This works because if is an expression. The value of the expression is the value of the last expression in whichever branch was chosen. An if without an else always results in () as the value.