Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Module 8: Parameter Passing

Lecture 6: Thursday, May 28, 2026.
Code examples

In this module, we will learn about:

  1. How data can be passed to a function as a parameter: by clone, by const reference, by mutable reference, and by move.
  2. The benefits and disadvantages of each way of passing a parameter, and when to use each.

Forward on References

A reference is a way to refer to a value without taking ownership of it. References come in two flavors:

  • Const reference (&T): read-only access to the referenced value.
  • Mutable reference (&mut T): read and write access to the referenced value.

To create a reference to a value, we use the & operator — this is called borrowing. For a mutable reference, we use &mut. Notice in the example below that mutating through ref2_x1 also changes x1, since they both refer to the same value.

fn main() {
    let mut x1: i32 = 10;
    // This is a const ref.
    let ref_x1: &i32 = &x1;
    println!("ref_x1 refers to value {}", ref_x1);
    // This is a mut ref.
    let ref2_x1: &mut i32 = &mut x1;
    *ref2_x1 += 1;
    println!("ref2_x1 refers to value {}", ref2_x1);
    println!("x1 also changed {}", x1);
}

Rust enforces important rules about references at compile time. These ensure that references always refer to something, and that at most one reference can mutate the underlying data at any given time. We will look at these rules in depth later in the course.

Passing Data to Functions

One of the most common uses of references is passing data to functions. There are four main ways to pass data.

Pass by Const Reference

Passing a const reference (&T) is very fast and gives the function read-only access to the data.

fn midpoint(v: &Vec<i32>) -> usize {
    return v.len() / 2;
}

use std::time::{Instant};

fn main() {
    // Make a big vector with 1,000,000 elements.
    let mut my_vec = Vec::with_capacity(1000000);
    for i in 0..1000000 {
        my_vec.push(i);
    }

    let time = Instant::now();
    let mid = midpoint(&my_vec); // pass by const ref
    println!("Took {:?}", time.elapsed());
    println!("Mid point element is {}", my_vec[mid]);
}

Pass by Clone

Passing by clone creates an independent copy of the data for the function. Compare how long this takes versus passing by reference:

fn midpoint2(v: Vec<i32>) -> usize {
    return v.len() / 2;
}

use std::time::{Instant};

fn main() {
    // Make a big vector with 1,000,000 elements.
    let mut my_vec = Vec::with_capacity(1000000);
    for i in 0..1000000 {
        my_vec.push(i);
    }

    let time = Instant::now();
    let mid = midpoint2(my_vec.clone()); // pass by clone
    println!("Took {:?}", time.elapsed());
    println!("Mid point element is {}", my_vec[mid]);
}

Passing by clone is much slower than passing by reference, because clone() copies all elements of the vector into a brand new vector before passing it to the function.

Pass by Move

Passing by move transfers ownership of the data to the function. Try to compile the following code:

fn midpoint3(v: Vec<i32>) -> usize {
    return v.len() / 2;
}

use std::time::{Instant};

fn main() {
    // Make a big vector with 1,000,000 elements.
    let mut my_vec = Vec::with_capacity(1000000);
    for i in 0..1000000 {
        my_vec.push(i);
    }

    let time = Instant::now();
    let mid = midpoint3(my_vec); // pass by move
    println!("Took {:?}", time.elapsed());
    println!("Mid point element is {}", my_vec[mid]);
}

The compiler will produce an error: my_vec can no longer be used after being moved into midpoint3. Once data is moved into a function, the caller loses access to it. We will study why this is the case at a deeper level in a later lecture.

We can fix the error by not using my_vec after the move:

fn midpoint3(v: Vec<i32>) -> i32 {
    let mid = v[v.len() / 2];
    return mid;
}

use std::time::{Instant};

fn main() {
    // Make a big vector with 1,000,000 elements.
    let mut my_vec = Vec::with_capacity(1000000);
    for i in 0..1000000 {
        my_vec.push(i);
    }

    let time = Instant::now();
    let mid = midpoint3(my_vec); // pass by move
    println!("Took {:?}", time.elapsed());
    println!("Mid point element is {}", mid);
}

Pass by Mutable Reference

Passing a mutable reference (&mut T) is also very fast and gives the function both read and write access to the original data. Any changes the function makes will be visible in the caller.

fn add_0_to_vec(v: &mut Vec<i32>) {
    v.push(0);
}

fn main() {
    let mut v: Vec<i32> = Vec::new();
    add_0_to_vec(&mut v);  // pass by mut ref
    println!("{:?}", v);
}

Experiment with the above code. What happens if v were immutable (e.g., let v: Vec<i32> = Vec::new())?

Advantages and Disadvantages

We have the following ways to pass data to a function:

  1. Pass by clone:
    • pros: gives the function a separate copy of the data it can control and modify without affecting the original data.
    • cons: slow and uses extra memory.
  2. Pass by const reference:
    • pros: very fast, the owner of the data can relax knowing their data is not going to be modified.
    • cons: the function cannot modify the data.
  3. Pass by mutable reference:
    • pros: very fast; the function can modify the data.
    • cons: changes made by the function will affect the original variable in the caller.
  4. Pass by move:
    • pros: very fast and gives the function full ownership of the data.
    • cons: the original variable can no longer be used in the caller after the move.

Remember these pros and cons! We may ask you about them in the midterm :)

When Should You Use Each Of These?

Consider the following exercise questions:

  1. If you are asked to build a function that prints a given vector, would you choose to pass the vector by clone, const ref, mut ref, or move? Why?
  2. If you are asked to build a function that removes all even numbers from a vector, how would you pass the vector and why?
  3. If you are asked to build a function that creates a sorted copy of a vector while keeping the original vector unchanged, how would you pass the vector? Why?