Course outline

Rust / Basic / Lesson 4

Borrowing, slices, and strings

Use temporary access without transferring ownership, end conflicting borrows before mutation, and treat UTF-8 byte boundaries explicitly.

About 4 min readContent reviewed
On this page

Lesson goals

  • Distinguish shared and exclusive mutable borrowing.
  • Use &str and slices as borrowed interfaces.
  • Separate byte length from Unicode scalar and display counts.

A reference grants access, not a new owner

An &T reference provides shared access to a valid T for an appropriate lifetime. An &mut T provides exclusive mutable access under Rust borrowing rules. References do not own the referred allocation or extend an arbitrary owner lifetime. Several shared borrows can coexist, but an active exclusive borrow conflicts with other accesses to the same data. Interior-mutability types introduce controlled exceptions through their APIs, not permission to ignore aliasing. Start by writing which operation observes and which operation mutates, then choose reference forms to match those requirements.

Sequence access around the last use

The compiler often tracks a borrow until its last relevant use rather than automatically keeping it active until the closing brace. This permits a mutation phase followed by an observation phase without unnecessary nested blocks. In the first example append receives a short exclusive borrow and returns before a shared slice is created. The owner remains alive throughout printing. A mutable reference itself is not Copy; passing it may involve reborrowing, so avoid explaining all reference use as unrestricted duplication. The function signature makes the mutation visible to callers.

rust An exclusive phase followed by a shared view
fn append(text: &mut String) { text.push_str(" lab"); }
fn main() {
    let mut title = String::from("Rust");
    append(&mut title);
    let view: &str = &title;
    println!("{view}");
}
Expected output
Rust lab

append changes the owned String, then its borrow ends. The later &str borrows the resulting live text.

A slice carries extent with a borrow

A slice such as &[u32] views a contiguous sequence and its length without owning elements. &str is a borrowed UTF-8 string slice, while String is an owned growable UTF-8 string. Prefer &str for read-only text parameters so callers can provide literals, owned strings, or valid substrings. Indexing a slice requires a valid index and can panic if the bound is wrong; get returns Option for checked access. A slice cannot remain in use across a vector mutation that might invalidate its backing storage, even when spare capacity appears sufficient.

Text has several legitimate length measures

String and str length counts bytes. chars counts Unicode scalar values, which still differ from user-perceived grapheme clusters in texts with combining marks or joined sequences. Rust does not support integer indexing into a string as if each visible character occupied one byte. A byte range must lie on UTF-8 boundaries. The second example uses an Arabic word with three scalar values encoded in six bytes and obtains its first two-byte scalar through get. General display-width and grapheme processing require a deliberate Unicode policy beyond this standard-library demonstration.

rust Bytes and Unicode scalar values differ
fn main() {
    let word = "Ω†ΩˆΨ±";
    println!("{} {}", word.len(), word.chars().count());
    if let Some(first) = word.get(0..2) { println!("{first}"); }
}
Expected output
6 3
Ω†

The UTF-8 encoding has six bytes and three scalars. The range 0..2 is a valid boundary pair for the first letter.

Change the design before fighting diagnostics

When a borrow conflict occurs, locate the last use of every reference and the proposed mutation. Often the correct repair is to finish reading, store a small owned result, then mutate. If two disjoint portions must be changed simultaneously, a safe API such as split_at_mut can establish their non-overlap. Adding clone can be appropriate for a required snapshot but should not replace understanding the conflict. Do not cast to raw pointers to bypass a beginner borrowing error. That transfers obligations into unsafe code instead of proving the original access pattern valid.

Common errors and fixes

E0499 reports overlapping mutable borrows.

Why it happens
Two exclusive accesses remain live together.
How to fix it
End the first use or split provably disjoint data safely.
Prevent it next time
Keep mutation phases short and explicit.

E0502 reports a read/write borrow conflict.

Why it happens
A shared reference is still used after mutation begins.
How to fix it
Finish observation before acquiring the mutable borrow.
Prevent it next time
Track last uses rather than adding arbitrary braces.

A substring operation rejects a byte range.

Why it happens
A boundary cuts through a UTF-8 encoding.
How to fix it
Use valid boundaries or iterate chars for scalar processing.
Prevent it next time
Specify the desired text unit in requirements.

Check your understanding

1. A function reads a name and returns its byte count while the caller retains the String. Write the signature and predict the result for "Mira".

Show worked solution

Use fn byte_count(name: &str) -> usize and return name.len(). Calling byte_count(&owned_name) borrows rather than moves its String. Mira has four ASCII bytes, so the result is four. The caller may later mutate the string after the borrow ends, provided no reference from that borrow remains live.

2. For the Arabic word in the example, explain get(0..1) and get(2..4), including how callers should handle the results.

Show worked solution

get(0..1) returns None because one is inside the first two-byte scalar. get(2..4) returns Some("و") because both endpoints are valid boundaries around the second scalar. Match the Option or use if let rather than unwrapping an uncertain range. Neither byte range defines a universal method for extracting a user-visible character in arbitrary Unicode text.

Key takeaways

  • Borrow only for the access duration required.
  • Treat &str and slices as lifetime-dependent views.
  • Choose bytes, scalar values, or graphemes explicitly.

Official references