Syntax Cache
BlogMethodFeaturesHow It WorksBuild a Game
  1. Home
  2. Rust
  3. Rust Strings Practice: String vs &str, Ownership & Methods
Rust59 exercises

Rust Strings Practice: String vs &str, Ownership & Methods

Practice Rust string handling including String vs &str, string slices, conversion methods, and UTF-8 encoding with hands-on exercises.

Cheat SheetCommon ErrorsQuick ReferencePractice
Warm-up1 / 2

Can you write this from memory?

Create a String variable `name` with the value "Alice".

On this page
  1. 1String vs &str Cheat Sheet
  2. 2Compiler Error E0308: Expected String, Found &str
  3. 3Compiler Error E0277: String Cannot Be Indexed by Integer
  4. 4Compiler Error E0502: Mutable Borrow While Slice Is Active
  5. 5Safe Slicing with get()
  6. 6Bytes vs Characters vs Graphemes
  7. 7Conversion Methods
  8. 8Function Parameters: Prefer &str
  9. 9Building Strings
  10. 10Further Reading
String vs &str Cheat SheetCompiler Error E0308: Expected String, Found &strCompiler Error E0277: String Cannot Be Indexed by IntegerCompiler Error E0502: Mutable Borrow While Slice Is ActiveSafe Slicing with get()Bytes vs Characters vs GraphemesConversion MethodsFunction Parameters: Prefer &strBuilding StringsFurther Reading

Rust has two main string types: String (owned, heap-allocated, growable) and &str (borrowed slice, usually a view into a String or a string literal).

The distinction trips up every Rust learner. When do you use String? When &str? How do you convert between them? Why can't you index into a string?

This page focuses on the string operations you'll use constantly, from conversions to safe slicing to the UTF-8 gotchas that catch everyone.

Related Rust Topics
Rust Ownership & Borrowing: Move Semantics & Borrow RulesRust Collections & Iterators: Vec, HashMap, Iterator Adapters

Take &str for function parameters (accepts both types via deref coercion). Use String when you need ownership or mutation.

String&str
OwnershipOwned, heap-allocatedBorrowed slice (view into data)
MutabilityGrowable with push_str, pushImmutable
Use forStoring, building, returningFunction parameters, string literals
Size3 words (ptr + len + capacity)2 words (ptr + len)
Literal type—"hello" is &'static str

Rule of thumb: take &str for input parameters (accepts both String and literals via deref coercion). Use String when you need ownership or mutation. For a copy-ready reference of all string types and conversions, see the Rust string types cheat sheet.

Ready to practice?

Start practicing Rust Strings: String vs &str, Ownership & Methods with spaced repetition

When a struct or function wants String but you have a literal, call .to_string(), String::from(), or .to_owned().

The most common string error. A struct or function wants an owned String but you passed a literal:

struct Config {
    name: String,
}

// WRONG: "app" is &str, not String
// let c = Config { name: "app" };  // error[E0308]

// RIGHT: convert to String
let c = Config { name: String::from("app") };
// or
let c = Config { name: "app".to_string() };
// or
let c = Config { name: "app".to_owned() };

Rust strings are UTF-8 encoded. A single character can be 1--4 bytes, so s[0] would be ambiguous (byte? character? grapheme?):

// WRONG: direct indexing
// let s = String::from("hello");
// let c = s[0];  // error[E0277]: String cannot be indexed by {integer}

// RIGHT: use .chars() for character access
let s = String::from("hello");
let c = s.chars().nth(0);  // Some('h')

// RIGHT: byte slicing when you know the boundaries
let first_byte = &s[0..1];  // "h" — safe for ASCII

An &str slice borrows the String. The ownership and borrowing rules apply directly here: you cannot mutate the String while the slice is alive:

let mut s = String::from("hello world");

// WRONG: slice borrows s, then we try to mutate
// let first = &s[..5];
// s.push_str("!");  // error[E0502]
// println!("{first}");

// RIGHT: scope the slice, or copy what you need
let first = s[..5].to_string();  // copy into owned String
s.push_str("!");                 // no active borrow
println!("{first}");

Never use &s[a..b] on untrusted ranges. Use s.get(a..b) which returns Option<&str> instead of panicking.

&s[a..b] panics if the range lands inside a multi-byte character. Use get() for non-panicking slicing:

let s = "cafe\u{0301}";  // "café" — e + combining accent

// PANICS: byte 5 is inside the combining accent
// let bad = &s[0..5];

// SAFE: get() returns None on invalid boundaries
let safe = s.get(0..4);  // Some("cafe")
let invalid = s.get(0..5);  // None (not a char boundary)

// Check boundaries explicitly
if s.is_char_boundary(4) {
    let slice = &s[0..4];  // "cafe"
}

Make get() your default for any range that comes from user input or computation.

.len() counts bytes, not characters. These three measurements can all differ:

let s = "cafe\u{0301}";  // "café" (e + combining accent)

s.len()              // 6 bytes (UTF-8 encoded)
s.chars().count()    // 5 Unicode scalar values (c, a, f, e, combining accent)
// grapheme clusters: 4 ("c", "a", "f", "é") — need unicode-segmentation crate
MethodCountsUse for
.len()BytesBuffer sizes, capacity, I/O
.chars().count()Unicode scalar valuesCharacter-level processing
Graphemes (external crate)User-perceived charactersDisplay width, text editing

For grapheme clusters, use the unicode-segmentation crate: UnicodeSegmentation::graphemes(s, true).count().

// &str → String (three equivalent ways)
let s1 = "hello".to_string();
let s2 = String::from("hello");
let s3 = "hello".to_owned();

// String → &str (deref coercion or explicit)
let owned = String::from("hello");
let borrowed: &str = &owned;          // deref coercion
let also_borrowed = owned.as_str();   // explicit

// Takes &str: accepts both String and &str via deref coercion
fn greet(name: &str) {
    println!("Hello, {name}!");
}

let owned = String::from("world");
greet(&owned);     // String auto-derefs to &str
greet("world");    // &str passed directly

let mut s = String::new();
s.push_str("hello");
s.push(' ');
s.push_str("world");

// format! for complex concatenation (most readable)
let combined = format!("{} {} {}", year, month, day);

For hot loops, pre-reserve capacity and use push_str. If you're building strings from collection data, the iterators guide shows how to chain .map() and .collect::<String>() for the same effect:

let mut buf = String::with_capacity(1024);
for line in lines {
    buf.push_str(line);
    buf.push('\n');
}

For compile-time literal concatenation, use concat!:

const GREETING: &str = concat!("hello", " ", "world");  // &'static str

  • The Rust Book: Strings — String vs &str, UTF-8, slicing
  • Rust By Example: Strings — practical string patterns
  • std::string::String — full method reference
  • unicode-segmentation crate — grapheme cluster iteration

When to Use Rust Strings: String vs &str, Ownership & Methods

  • Use `&str` for function parameters when you only need to read the string.
  • Use `String` when you need ownership or need to modify the string.
  • Use `format!()` for complex string concatenation.
  • Use `.push_str()` to append to a String efficiently.
  • Use `.chars()` to iterate over Unicode characters.

Check Your Understanding: Rust Strings: String vs &str, Ownership & Methods

Prompt

What is the difference between String and &str in Rust?

What a strong answer looks like

String is an owned, heap-allocated, growable UTF-8 string. &str is a borrowed slice (a view) into string data. String literals are &str. You can borrow a String as &str, but converting &str to String requires allocation.

What You'll Practice: Rust Strings: String vs &str, Ownership & Methods

Convert between String and &strUse &str for function parameters (deref coercion)Build strings with push_str(), format!, concat!Slice safely with get() and is_char_boundary()Distinguish len() (bytes) from chars().count()Iterate with .chars(), .bytes(), .split()Pre-reserve capacity for hot loopsFix E0308, E0277, E0502 string errors

Common Rust Strings: String vs &str, Ownership & Methods Pitfalls

  • `.len()` returns a smaller number than expected -- `.len()` counts bytes, not characters. A multi-byte emoji like "\u{1F980}" is 4 bytes. Use `.chars().count()` for character count.
  • `s[0]` won't compile -- Rust strings are UTF-8 and indexing by integer is not allowed because byte position may not align with character boundaries. Use `.chars().nth(0)` for the first character.
  • Program panics on `&s[0..n]` -- slicing at a byte offset inside a multi-byte character panics. Use `s.get(0..n)` which returns `Option<&str>`, or check `.is_char_boundary(n)` first.
  • String grows slowly in a loop using `+` -- each `+` allocates a new String and moves the left operand. Use `push_str()` on a single `mut String`, ideally with `String::with_capacity()` pre-allocated.
  • `.chars().count()` gives wrong count for emoji/accented text -- `.chars()` counts Unicode scalar values, not user-perceived characters. The string "cafe\u{0301}" has 5 chars but 4 visible characters. For grapheme clusters, use the `unicode-segmentation` crate.
  • Function rejects your `&String` argument -- deref coercion usually converts `&String` to `&str` automatically, but trait bounds or generics can block it. Call `.as_str()` explicitly when coercion fails.

Rust Strings: String vs &str, Ownership & Methods FAQ

Why can't I index a string with s[0]?

Rust strings are UTF-8, where characters can be 1-4 bytes. Indexing by byte position could slice in the middle of a character. Use `.chars().nth(0)` for the first character or byte slicing `&s[0..1]` if you know the byte boundaries.

When should I use String vs &str in function parameters?

Prefer `&str` for input parameters—it accepts both String (via deref coercion) and &str. Use `String` when the function needs to take ownership or return an owned string.

How do I concatenate strings?

Use `format!()` for readability, `push_str()` for efficiency when building incrementally, or `+` operator (which consumes the left String). Avoid repeated `+` in loops—use `push_str()` instead.

What does .len() return for strings?

`.len()` returns the number of bytes, not characters. For character count, use `.chars().count()`. For "🦀".len() you get 4 (bytes), but .chars().count() gives 1.

How do I convert between String and &str?

&str to String: `.to_string()`, `String::from()`, or `.to_owned()`. String to &str: `&s`, `s.as_str()`, or automatic deref coercion in function calls.

How do I slice a string safely without panicking?

Use `s.get(start..end)` which returns `Option<&str>` instead of panicking. Or check `s.is_char_boundary(n)` before using `&s[..n]`. Never use raw byte slicing on untrusted input.

What is the difference between chars() and graphemes?

`.chars()` yields Unicode scalar values. Grapheme clusters are user-perceived characters (e.g., "e" + combining accent = one grapheme but two chars). For graphemes, use the `unicode-segmentation` crate.

Rust Strings: String vs &str, Ownership & Methods Syntax Quick Reference

String literal
let s: &str = "hello";
Owned String
let s = String::from("hello");
To String
let s = "hello".to_string();
Borrow as &str
let slice: &str = &my_string;
Push string
s.push_str("world");
Push char
s.push('!');
Format
let s = format!("{} {}", a, b);
Iterate chars
for c in s.chars() {
    println!("{c}");
}
String slice
let slice = &s[0..5];
Safe slice
let safe = s.get(0..5);  // Option<&str>
Char count
let count = s.chars().count();
With capacity
let mut s = String::with_capacity(256);
concat! (compile-time)
const S: &str = concat!("hello", " ", "world");

Rust Strings: String vs &str, Ownership & Methods Sample Exercises

Example 1Difficulty: 1/5

Fill in the method to convert a &str to a String.

to_string
Example 2Difficulty: 2/5

Fill in the method that creates an owned String from a borrowed &str.

to_owned
Example 3Difficulty: 1/5

Fill in the type of a string literal.

&str

+ 56 more exercises

Quick Reference
Rust Strings: String vs &str, Ownership & Methods Cheat Sheet →

Copy-ready syntax examples for quick lookup

Further Reading

  • Rust Newtype Pattern: Catch Unit Bugs at Compile Time18 min read

Also in Other Languages

Python String Methods Practice: f-strings, split, join, strip, slicingJavaScript String Methods Practice

Start practicing Rust Strings: String vs &str, Ownership & Methods

Free daily exercises with spaced repetition. No credit card required.

← Back to Rust Syntax Practice
Syntax Cache

Build syntax muscle memory with spaced repetition.

Product

  • Pricing
  • Our Method
  • Daily Practice
  • Design Patterns
  • Interview Prep

Resources

  • Blog
  • Compare
  • Cheat Sheets
  • Vibe Coding
  • Muscle Memory

Languages

  • Python
  • JavaScript
  • TypeScript
  • Rust
  • SQL
  • GDScript

Legal

  • Terms
  • Privacy
  • Contact

© 2026 Syntax Cache

Cancel anytime in 2 clicks. Keep access until the end of your billing period.

No refunds for partial billing periods.