Using references in Rust is called Borrowing.

Here is a simple example:

fn main() {
	let s1 = String::from("Let's get Rusty");
	print_string(&s1);
	print_string(&s1);
}
 
fn print_string(s: &str) {
	println!("{s}");
}

There are two borrowing rules:

1. at any given time, you can have either one mutable reference or any number of immutable references

example:

fn main() {
	let s1 = String::from("Let's get Rusty");
	let r1 = &s1;
	let r2 = &s1;
	print_string(r1);
}
 
fn print_string(s: &str) {
	println!("{s}");
}

borrowing is not violated here because these references (and original value) is immutable (no values were manipulated only read; not updated/written)

but if we do this instead (add themut keyword for s1)

fn main() {
	let mut s1 = String::from("Let's get Rusty");
	let r1 = &s1;
	let r2 = &mut s1;
	// cannot borrow 's1' as mutable because it is also borrowed as immutable.
	print_string(r1);
}
 
fn print_string(s: &str) {
	println!("{s}");
}

if you get the next reference then you are understanding why this is important:

fn main() {
	let mut x = vec![1, 2, 3];
 
	let last = x.last().unwrap();
 
	x.push(4);
 
	println!("{:?}", last);
}

Why would this not work?

Correct Code:

fn main() {
	let mut x = vec![1, 2, 3];
 
	x.push(4);
 
	let last = x.last().unwrap();
 
	println!("{:?}", last);
}

This way there is only one mutable reference to x.

2. References must always be valid.

fn main() {
	let r;
	{
		let s = String::from("lets get rusty");
 
		r = &s;
	}
		// borrowed value (&s) does not live long enough.
 
	println!("{}", r);
}

since the r owner definition was dropped when leaving the inner scope.

the println! statement would not work due to no borrow / references existing on the heap / memory.

The does not live long enough is a Rust attribute called Lifetimes