So, let’s first annotate all the types in the examples to avoid any potential confusion
fn test() -> &'static str{
let mut i: &'static str = "asdf";
let remainder: &mut &'static str = &mut i;
let another_ref_i: &mut &'static str = remainder;
another_ref_i
}
fn test() -> &'static str{
let mut i: &'static str = "asdf";
let remainder: &mut &'static str = &mut i;
let &mut another_ref_i = remainder;
// NOTE: another_ref_i: &'static str
another_ref_i
}
Tjhe &mut in the let &mut another_ref_i line is a reference pattern. It’s functionality is to de-reference what it’s being matched against, or in other words to follow the pointer.
For example when you have something like
x: Option<&mut (i32, &mut bool)>
you can write something like
match x {
Some(&mut (42, &mut true)) => /* ... */
/* ... */
}
note that there are newer special rules in Rust, so-called ergonomics, that will actually make
match x {
Some((42, true)) => /* ... */
/* ... */
}
work as-well, thus &mut patterns can be kind-of uncommon nowadays.
In your particular example, a more ideomatic (and equivalent) way to write
let &mut another_ref_i = remainder;
would be
let another_ref_i = *remainder;
And actually, in case you’re wondering why the code without the &mut works as-well, we can talk about why this version
// NOTE: remainder: &mut &'static str
let another_ref_i: &'static str = remainder;
compiles as-well. The reason is that there’s an implicit (dereferencing) coercion happening here. The same is true for the return value in your first code example. The variable another_ref_i doesn’t have the right type to be returned from the function, but it can be coerced into the right type by dereferencing.