Suppose a string x declared in main that is used as an argument for a function helper(x : String).
helper(x) in Java, we give helper a reference to x.helper(x) in Rust, we give helper ownership of x.On Rust’s Memory Management:
- Unlike malloc and free, memory management is about using and not using variables.
- When a function returns, all the data it owns gets dropped.
Let’s look at this Rust code:
fn main() {
let x = format!("Hello World");
helper(x);
helper(x);
}
fn helper(x : String) {
println!(x);
}This code cannot compile because when we call helper the first time, we give ownership of x to it; and so when helper returns, it drops x. As a result, because we try to use x on the second helper(x), we get error[E0382]: use of moved value
Let’s look at this Java code:
void main() {
Vector x = ...;
helper(x);
helper(x);
}
void helper(Vector x) {
println!(x);
}In Java, we give access (references) to things rather than ownership, which entangles their states. When helper modifies x in Java, it’s immediately visible to main.
Moreover, we can never know when to free memory; even if main exits, due to the possibility of helper(x) making threads.
As a result of everything being implicitly shared, data races are a common issue. And since we can’t know when things will be freeable until runtime, we just run the garbage collector when we need more memory, which pauses execution and searches for stuff to free.
Non-Copyable: Values move from place to place.
Clone: Run custom code to copy a value.
fn main() {
let x = format!("Hello World");
helper(x.clone());
helper(x);
}Copy: Some basic data types are automatically cloned when you use them, as it’s very cheap.
You can never have a reader/writer at the same time.
Both locks last until their reference goes out of scope.
Shared Borrow (&x): We can borrow variables to create a reference.
More on Borrowed == Immutable:
Mutation is allowed:
- In controlled scenarios with specific APIs (e.g., mutex), or
- When a
mutvalue is shared borrowed, it can’t be mutated during the borrow—but regains mutability afterward. (see “Example: Immutability of Shared Borrows”)
As seen in the previous example, this code doesn’t compile.
fn main() {
let x = format!("Hello World");
helper(x);
helper(x);
}
fn helper(x : String) {
println!(x);
}In this fix, we borrow the string to create a reference and update helper to take a reference to a string.
fn main() {
let x = format!("Hello World");
let r = &x;
helper(r);
helper(r);
}
fn helper(x : &String) {
println!(x);
}r is a copy type!This is the code from the previous example, with a key change: We made made x mutable, and even push a character to it.
Shouldn’t this not compile because shared referrences are immutable?
fn main() {
let mut x = format!("Hello World");
x.push("a");
let r = &x;
helper(r);
helper(r);
}
fn helper(x : &String) {
println!("{}", x);
}The reason the code compiles is that Rust sees x as mutable (as we declared it), until it gets borrowed.
r exists, we’ll get errors.// Does not compile (borrowed == immutable)
fn main() {
let mut x = format!("Hello World");
let r = &x;
helper(r);
helper(r);
x.push("a");
}// Does compile
fn main() {
let mut x = format!("Hello World");
{
let r = &x;
helper(r);
helper(r);
}
x.push("a");
}x once r is out of scope.Mutable Borrows (&mut x):
This Rust code uses borrowing to concatenate two strings:
pub fn main() {
let (mut str1, str2) = two_words();
str1 = join_words(str1, str2);
println!("concatenated string is {:?}", str1);
}
fn two_words() -> (String, String) {
(format!("fellow"), format!("Rustaceans"))
}
fn join_words(mut prefix: String, suffix: String) -> String {
prefix.push(' ');
for ch in suffix.chars() {
prefix.push(ch);
}
prefix
}This is a modification of the code to use borrowing and modify str1 in place:
pub fn main() {
let (mut str1, str2) = two_words();
join_words(&mut str1, &str2);
println!("concatenated string is {:?}", str1);
}
fn two_words() -> (String, String) {
(format!("fellow"), format!("Rustaceans"))
}
fn join_words(prefix: &mut String, suffix: &String) {
prefix.push(' ');
for ch in suffix.chars() {
prefix.push(ch);
}
}