No, it's about mut versus non-mut variable bindings. Without mut you can't overwrite the variable or take a &mut to it. Also, the &mut s points to s, not directly to the heap memory containing the textual data.
&mut s s [stack] [heap]
+------+ +-----+-----+----------+ +---+---+---+---+---+
| 0x...| ---> | ptr | len | capacity | ,--> | h | e | l | l | o |
+------+ +-----+-----+----------+ | +---+---+---+---+---+
Your push modifies the value on the stack in addition to writing to the heap -- at a minimum it changes the length. In the case of needing to grow the allocation, it also changes the capacity and probably the pointer to the heap as well.
Or here's a version that definitely overwrites all of s on the stack, in case that's easier to understand.
It absolutely is connected with the original variable. That's the whole point. If it weren't, that would be a copy, not a borrow.
This has nothing to do with the heap. I don't know where people get the idea from that this somehow only applies to heap-allocated values. The borrow checker doesn't even have a concept of heap. References to heap-allocated data and stack-allocated data don't have different types. A reference is a reference, and it can point anywhere.
Looks like the required mut annotation did its job very well, as changing the var is exactly what will happen
A reference in Rust will always avoid (the overhead of [implicitly]) copying the data. If you want to have a separate copy that you can mutate without overhead, then you need to make a copy (usually via .clone()) which can then be passed by value. If you don't want the original changed but don't need to mutate anything either then immutable references (&T) work fine.
In some cases, you might not know in advance whether or not you want to mutate the data. To spare the overhead of copying in case you don't mutate, but keep the original untouched if you do, there is the concept of "copy on write" of copying the data only if/once you want to mutate something - which in Rust can be done using two types: Cow<'_, T> is a type for doing copy-on-write while the borrow checker is still responsible for the non-copying case (treating it like a &T reference) - and there's Rc<T> and Arc<T> in case you don't want any connection, even in lifetime, to your original value (which would also be wrapped in an Rc or Arc respectively), and those offer copy-on-write functionality via their .make_mut() methods.