Rust: references

In my previous post I briefly touched on the ownership model of Rust, which displayed how only a singular variable can hold a given value at a time. To recap:

fn main() {
	// Ownership is not a concern for anything which has the `Copy` trait, e.g.

	let num1: i32 = 12;
	let num2 = first_num;
	println!("Nums: {}, {}", num1, num2);
	// ^ this would compile error-free

	// However, this...

	let name: String = String::from("Jim");
	let some_guy = name;
	println!("Someone: {}, {}", some_guy, name);
	// ^ there would be a `use of moved value` error here because `name`'s value
	//   was moved to `some_guy` and now it (`name`) is no longer initialized
}

There's a little more nuance to it than that and the intricacies of Copy should certainly be investigated further, but what you see above is a good example of ownership. That being said you might be asking how anyone could get anything done in Rust if they're only allowed one variable to hold a given value at a time. Well, another way to do that is to create a reference to a value via Rust's & expression. References allow us to get to the values of variables while avoiding the ownership paradigms. That's where the catch is. We only get to access the value, we don't get to mutate the value (more on this below). Let's see another example:

fn main() {
	let name: String = String::from("Dude");
	let some_dude = &name;
	println!("{}", some_dude); // Dude
	println!("{}", name); // Dude
	// ^ compiles as expected
}

What we did immediately above is very close to what we did in the recap of ownership near the top, but instead of losing name due to ownership changes we instead initialized some_dude with a reference to name. some_dude now holds the same value as name, and name stays initialized. That's an example of a read-only (nonowner) reference. In order to do any mutations we have to create a mutable reference, i.e. &mut <variable name>:

fn change_name(x: &mut String) {
	*x = String::from("steve"); // `*` operator explained below
}

fn main() {
	let mut name: String = String::from("jim");
	{
		let some_dude = &mut name;
		println!("{}", some_dude); // "jim"
		change_name(some_dude);
		println!("{}", some_dude); // "steve"
	}
	println!("{}", name); // "steve"
	// ^ without mutable references this would not compile
}

While this example isn't too realistic (why would you do this at all?), it does give a great insight into passing around mutable references. We only let some_dude exist within a sub-scope of the main function. In this sub-scope we pass some_dude to the change_name function and reassign it the value steve. This works here because we use the Deref trait that is available (much like the Copy trait) to certain types. What * does is access the value behind a reference. In this case *x, which in this example is *some_dude, really says name. So what we're doing in change_name() is name = String::from("steve"); And because name was originally defined as mutable the compiler doesn't complain. Running this program gives us the output:

jim
steve
steve

Which demonstrates that we in fact did change the value of the original name variable.

Ownership, moving, referencing, dereferencing...Rust doesn't make it easy, but it does make the end result very safe and very fast.

comment

Comments