Borrow a String through shared or mutable references and understand when each is valid. The examples connect reference syntax, dereferencing, lifetimes, and data-race prevention.
Borrow a String through shared or mutable references and understand when each is valid. The examples connect reference syntax, dereferencing, lifetimes, and data-race prevention.
Rust references let a function use data without taking ownership of it. A shared reference uses &T; a mutable reference uses &mut T. Start with Rust ownership if moves are new to you, then follow these examples of borrowing a String.
Passing an owned, non-Copy value such as String by value moves it into the function. Passing a reference lets the function access the original value without taking ownership. Values of Copy types, such as integers, are copied instead.
Consider the following function:
fn do_something(s: &String) {
println!("{}", s);
}RustHere, the & symbol is used to create a reference to the String rather than moving the String itself. The reference points to the value without taking ownership of it. This means the original variable retains ownership, and the function merely borrows it for a while.
let s1 = String::from("abc");
do_something(&s1);RustIn this example, s1 remains valid because do_something receives a shared reference rather than taking ownership. A borrow only needs to remain active while the reference is used; it does not necessarily last until the end of the surrounding lexical scope.
Here’s how this looks:
s (reference) s1 (owner)
|-----|---| |-----|---|
| ptr | | ------> | ptr | |
|-----|---| | len | 3 |
| cap | 3 |
|-----|---|
PlaintextWhen you create a reference to s1, Rust internally creates a pointer to the value s1. This pointer allows the function to access the value without owning it. Rust automatically manages the creation and destruction of these pointers, ensuring they are always valid.
One of Rust’s most powerful features is its ability to enforce lifetimes, ensuring that references do not outlive the data they point to. This avoids common bugs in other languages, such as dangling pointers or null references.
A shared reference, &T, does not allow ordinary mutation of the referenced value. A mutable reference, &mut T, provides exclusive access that permits mutation. This is separate from whether the variable holding the reference can be reassigned.
fn do_something(s: &mut String) {
s.push_str("def");
}
let mut s1 = String::from("abc");
do_something(&mut s1);
println!("{}", s1);RustThe call passes a mutable reference to s1 into do_something. The function can modify the string through that reference. The following diagram shows the relationship:
s (mutable reference) s1 (owner)
|-----|---| |-----|---|
| ptr | | ------> | ptr | |
|-----|---| | len | 3 |
| cap | 3 |
|-----|---|PlaintextRust has a unique feature with the dot operator (.). When you use the dot operator for method calls or accessing fields, Rust automatically dereferences down to the actual value, so you don’t need to worry whether you’re dealing with a value or a reference.
However, if you need to manually dereference a reference, you can use the * operator:
fn do_something(s: &mut String) {
(*s).push_str("def");
}RustThe dereference operator (*) has lower precedence than the dot operator. This means you sometimes need to use parentheses to clarify your intentions. For example:
fn do_something(s: &mut String) {
*s = String::from("def");
}RustThe assignment dereferences s and replaces the borrowed String with a new value.
For a given value, you can have either one active mutable borrow or multiple active shared borrows. The relevant period is when the references are used. This prevents conflicting access to that value; the Rust Book explains the rules and reference lifetimes.
Let’s consider a scenario where two threads are borrowing references to the same variable:
Thread A
ref1
|----|--|
| ptr| |
|----|--| ----------> Thread C
s1
Thread B --------> |----|----| |-----|
ref2 | ptr | | --> | a |
|----|--| | len | 3 | | b |
| ptr| | | cap | 3 | | c |
|----|--| |----|----| |-----|PlaintextIn a multithreaded environment, having multiple mutable references to the same variable can lead to unpredictable behavior, data corruption, or crashes. Rust’s borrowing rules prevent this by ensuring that only one mutable reference exists at any time, or multiple immutable references exist simultaneously but without any mutable reference.
Borrowing separates access from ownership. Use a shared reference for reading and a mutable reference when the function needs exclusive access to modify a value. Revisit String and &str to apply these choices to text parameters, or use the Rust Fundamentals series to review earlier lessons.

Senior Technical Architect at HCLTech · working with Leonteq Security AG
I have 10+ years of experience building distributed systems and fintech platforms. I write about practical, non-obvious engineering details that official documentation often skips.
Scala, JVM, AI, and backend systems. I send practical articles when there is something worth reading.
Join the newsletter