ProgrammingFullstack Developer

How does working with immutable strings and the dynamic type String in Rust work? What is the difference between String and &str, how does ownership work, and how can you safely convert between these types?

Pass interviews with Hintsage AI assistant

Answer.

Background:

Compared to native languages (C/C++), Rust builds safe string operations through a strict separation of reference (&str) and ownership (String) types. This eliminates most issues related to incorrect memory, buffer overflows, and double-free errors.

The Problem:

Unlike mature GC languages, where any string lives in managed memory, in Rust, you need to clearly understand who owns the string, how long it lives, and how to avoid dangling references after modifications. Working with UTF-8 strings requires care in indexing and modification.

The Solution:

In Rust, String is a mutable, heap-allocated string that owns its contents. &str is an immutable reference to a byte sequence with UTF-8 guarantee. If necessary, you can safely convert between them (&str -> String and vice versa) using standard methods.

Code Example:

fn main() { let owned: String = String::from("Rust"); let borrowed: &str = &owned; let primitive: &str = "Hello"; // literal is always &str // Converting &str -> String let s: String = primitive.to_string(); // Converting String -> &str let st: &str = &s; println!("{} {} {} {}", owned, borrowed, primitive, st); }

Key Features:

  • Explicit separation between ownership and reference (heap vs slice)
  • Safety conversion methods between String and &str are efficient and transparent regarding object lifetime
  • String literals always have type &'static str, not String

Trick Questions.

Why can't you index a string like s[1] or s[i]?

Rust strings are UTF-8, so indexing is not directly available: s[i] does not return the i-th character and can sometimes lead to a panic when accessing an incorrect byte boundary. Instead, use methods like .chars().nth(i) or .get(start..end).

Can you safely modify &str?

No - &str is always an immutable slice. For modifications, use to_owned/to_string, or use String/Vec<u8>.

What is the fundamental difference between String::from("abc") and "abc".to_string()?

These options are equivalent in result, both create a String by copying data from &str. The difference is only in style: for example, to_string is implemented through the ToString trait, while String::from more clearly expresses the intent "create ownership".

Common Mistakes and Anti-Patterns

  • Trying to index a string s[1] or s[0] to retrieve char
  • Implicit conversions without specifying lifetimes: returning references to temporary objects
  • Using String where &str is sufficient (unnecessary allocation)

Real-life Example

Negative Case

A function accepted String and made unnecessary string copying inside (clone), then wrote a slice in another function, forgetting to extend the lifespan of the source. Result: dangling reference & crash.

Pros:

  • Similar to familiar languages: you can easily get a "copy" of a string

Cons:

  • Performance loss due to unnecessary allocations
  • Possible leakage of references to temporary values

Positive Case

The function accepts &str, and if modifications are needed, it calls .to_string() inside, while all logic outside remains "zero copy". Lifetimes are under control, no unnecessary allocations.

Pros:

  • High performance
  • Ownership errors are prevented

Cons:

  • Need to understand lifetimes and ownership
  • Slightly more cognitive load for beginners