https://www.snoyman.com/blog/2018/11/rust-crash-course-05-rule-of-three

I also blog frequently on the Yesod Web Framework blog, as well as the FP Complete blog.

See a typo? Have a suggestion? Edit this page on Github

In this lesson, we’re going to cover what I’m dubbing the “rule of three,” which applies to function parameters, iterators, and closures. We’ve already seen this rule applied to function parameters, but didn’t discuss is so explicitly. We’ll expand on parameters, and use that to launch into new information on both iterators and closures.

This post is part of a series based on teaching Rust at FP Complete. If you’re reading this post outside of the blog, you can find links to all posts in the series at the top of the introduction post. You can also subscribe to the RSS feed.

Types of parameters

The first thing I want to deal with is a potential misconception. This may be one of those “my brain has been scrambled by Haskell” misconceptions that imperative programmers won’t feel, so apologies if I’m just humoring myself and other Haskellers.

Do these two functions have the same type signature?

fn foo(mut person: Person) { unimplemented!() }
fn bar(person: Person) { unimplemented!() }

The Haskeller in me screams “they’re different!” However, they’re exactly the same. The inner mutability of the person variable in the function is irrelevant to someone calling the function. The caller of the function will move the Person value into the function, regardless of whether the value can be mutated or not. We’ve already seen a hint of this: the fact that we can pass an immutable value to a function like foo:

fn main() {
    let alice = Person { name: String::from("Alice"), age: 30 };
    foo(alice); // it works!
}

With that misconception out of the way, let’s consider two other similar functions:

fn baz(person: &Person) { unimplemented!() }
fn bin(person: &mut Person) { unimplemented!() } 

Firstly, it’s pretty easy to say that both baz and bin have different signatures than foo. These are taking references to a Person, not a Person itself. But what about baz vs bin? Are they the same or different? You may be tempted to follow the same logic as foo vs bar and decide that the mut is an internal detail of the function. But this isn’t true! Observe:

fn main() {
    let alice = Person { name: String::from("Alice"), age: 30 };
    baz(&alice); // this works
    bin(&alice); // this fails
    bin(&mut alice); // but this works
}

The first call to bin will not compile, because bin requires a mutable reference, and we’ve provided an immutable reference. We need to use the second version of the call. And not only does this have a syntactic difference, but a semantic difference as well: we’ve taken a mutable reference, which means we can have no other references at the same time (remember our borrow rules from lesson 2).

The upshot of this is that there are three different ways we can pass a value into a function which appear at the type level: