Argument Passing and Returning Data in Rust

One of the things that’s “hard” about Rust when you first start learning it are the different ways you can pass an object to a function or return one, and the types associated with them.

For example, Strings come in a few different flavors, and you need to know which one to return them:

// This works:
fn return_string() -> String {
    let not_global = "I return ok, because I'm not global.".to_string();
    not_global  // Funky Rust return syntax.
}

// This doesn't, uncomment to see the error
/*
fn return_broken() -> String {
    let broken = "Not OK -- I'm a global literal!";
    broken
}
*/

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

Mutable methods vs immutable methods

Before learning about passing objects, we need to create a basic “object” and test it, which has mutable and immutable methods.

pub struct Person {
    name: String,
    state: String,
}

impl Person {
    // See https://rust-unofficial.github.io/patterns/idioms/ctor.html
    pub fn new(name: String, state: String) -> Self {
        Self {
            name: name,
            state: state
        }
    }

    pub fn print(&self) {
        println!("{} lives in {}.", self.name, self.state);
    }
    
    // Note it's &mut, not mut&
    pub fn move_to(&mut self, state: String) {
        self.state = state;
    }
}

fn main() {
    let static_john = Person::new("John Lockwood".to_string(), "California".to_string());
    static_john.print();

    // Static John can't move! Next line is an error
    // static_john.move_to("North Carolina".to_string());

    // Mutable John can
    let mut john = Person::new("John Lockwood".to_string(), "California".to_string());

    john.print();

    john.move_to("North Carolina".to_string());
    john.print();
}

Passing Objects

Basically, there are about four ways things can get passed around:

  • Directly non-mutable
  • Directly mutable
  • By reference non-mutable
  • By reference mutable

Passing Objects Directly

This MOVES the object to a new owner. If you do this, you can’t use the reference anymore. It doesn’t matter if you pass it mutably or not. If you’re not passing a reference, you’re changing ownership.

fn pass_ownership_non_mutable(person: Person) {
    person.print();
}

fn pass_ownership_mutably(mut person: Person) {
    person.move_to("New York".to_string());
}

Calling:

    // Pass a person not by reference
    let john2 = Person::new("John".to_string(), "California".to_string());
    pass_ownership_non_mutable(john2);
    // Can't do -- borrowed after move:  E0382:
    // john2.print();


    // Note we don't need to declare this as mut here
    // for that it matches the function signature of pass_ownership_mutually
    let john3 = Person::new("John ".to_string(), "California".to_string());
    pass_ownership_mutably(john3);
    // Can't do -- borrowed after move:  E0382:
    // john3.print();

Passing By Reference

In contrast to passing a non-reference, passing a reference does not change the owner:

fn pass_reference_non_mutable(person: &Person) {
    person.print();
}

fn pass_reference_mutably(person: &mut Person) {
    person.move_to("Washington".to_string());
    person.print();    
}

Calling:

    let mut john3 = Person::new("John ".to_string(), "California".to_string());
    pass_reference_mutably(&mut john3);

    // Reusing, ownership not transfered!
    pass_reference_non_mutable(&john3);

Leave a Comment