Forgive me if you already understood this, but to make it clear again just in case, the key difference between your two pieces of code is the number of mutable references:
two mut references (bad)
let slice1 = &mut s;
let slice2 = &mut s;
one mut reference (good)
let mut s = String::from("aaa");
It's a little difficult to talk about because of the word "borrow" being used a little too widely.
Hear me out.
Does a borrow happen when making the reference
let slice1 = &mut s
or when passing that reference to a function
I think the correct term is that "passing the reference to a function" is the borrow and making the reference is... something else. "Creating a reference" I suppose. I don't think there's a special term for this, it's not new to rust.
What is new to rust is the borrow-checker, and the borrow checker will enforce the rules of References
- At any given time, you can have either one mutable reference or any number of immutable references.
- References must always be valid.
In other languages like C I think people had to try to enforce the rules of references themselves by just... being careful. Which is kind of terrifying, humans are really bad at this.
The term borrow is used to refer to passing a reference into a function because it contrasts with the term for passing by value, which is move. One moves a variable into a function when passing by value.
This all ties into the concept of ownership. When borrowing, the function that receives the reference doesn't own the variable, but if moving, then the function does own the variable.
I've not explained this to anyone (except myself) before, so apologies if that is a confusing mess
Edit: I corrected my inline quotes:
let slice1 = &mut s instead of
let &mut s = ... and
test1(slice1) instead of