Error

"expected struct String, found &str" — How to Fix

This error occurs because you are trying to pass a string slice (`&str`) where a string value (`String`) is required, or vice versa, without an explicit conversion.

The ownership trap

You write a function to log a user's name. You pass "Alice" and the compiler rejects you with E0308 (mismatched types). You wrap it in String::from, it compiles. You try to use that variable again, and the compiler hits you with E0382 (use of moved value). You feel like you're juggling chainsaws while the compiler watches and critiques your grip. This isn't a bug. It's Rust asking you to make a choice about who owns the data.

String owns the heap, &str borrows the view

Rust splits strings into two types because it needs to track memory precisely. String is the owner. It holds a pointer to heap memory, a length, and a capacity. When the String goes out of scope, Rust frees that memory. &str is a borrower. It's a string slice. It holds a pointer to some text and a length. It points to data owned by someone else. It never frees memory. It just looks at it.

Think of String as a physical book on a shelf. &str is a bookmark pointing to a page in that book. You can have infinite bookmarks. You can only have one book. If the book gets thrown away, the bookmarks become useless. Borrowed data dies when the owner drops it. Keep the owner alive.

Fix the mismatch

The error expected struct String, found &str means a function wants ownership, but you handed it a reference. Rust won't allocate memory automatically. It won't guess you want to copy the data. You have to say it.

/// Takes ownership of the name to store it permanently.
fn save_name(name: String) {
    println!("Saved: {}", name);
}

fn main() {
    // "Alice" is a &str. It lives in the binary, not the heap.
    // save_name expects a String. The types don't match.
    // save_name("Alice"); // Error: E0308 mismatched types

    // Fix: Allocate a new String and copy the data.
    save_name("Alice".to_string());
}

Calling .to_string() allocates a new buffer on the heap. It copies the bytes from the &str into that buffer. It returns a String wrapping that buffer. The original &str is untouched. The new String is the sole owner. When save_name finishes, it drops the String, freeing the heap memory. The &str in main still points to the static data in the binary. Nothing is leaked. Nothing is double-freed.

Convention aside: use .to_string() for string conversions. The method .to_owned() exists and works, but .to_string() is the community standard for text. It signals intent clearly. Don't fight the convention.

Don't allocate unless you have to. The compiler forces you to pay for what you use.

Memory layout and cost

Under the hood, String is three words of data. A pointer to the heap. The length of the text. The capacity of the buffer. &str is two words. A pointer and a length. No capacity. You can't grow a &str. If you try to push to a &str, the compiler rejects you.

This size difference matters. String is heavier. Passing a String by value moves three words. Passing a &str moves two words. Passing a &String moves two words (the reference), but it's semantically wrong. The community calls &String a "String anti-pattern". If you see fn foo(s: &String), rewrite it to fn foo(s: &str). &String coerces to &str automatically. Taking &str accepts literals, slices, and references to strings. Taking &String rejects literals and slices. It adds friction without benefit.

If your function just reads the string, ask for &str. Forcing a String allocation is a tax on your caller.

Real code: structs and APIs

In real applications, you often store strings in structs. Structs need owned data because they manage their own lifetime. You can't store a &str in a struct without lifetime annotations, and even then, the data must outlive the struct. For most use cases, the struct field should be a String.

/// Represents a user with an owned name.
struct User {
    name: String,
}

/// Creates a User from borrowed input.
fn create_user(input: &str) -> User {
    // input is borrowed. User needs owned data.
    // We must clone the data into a String.
    User {
        name: input.to_string(),
    }
}

fn main() {
    let name_slice = "Alice";
    let user = create_user(name_slice);
    
    // user owns the String. name_slice is still valid.
    println!("User: {}, Input: {}", user.name, name_slice);
}

When you call create_user, the &str points to static data. Inside the function, .to_string() allocates a new String. The User struct takes ownership of that String. When create_user returns, the User moves to main. The String lives inside user. The &str name_slice is unaffected.

Sometimes you want flexibility. You're writing a library function. You want callers to pass String or &str without writing two versions. Use the AsRef<str> trait.

/// Processes text generically. Accepts String, &str, or anything that can become &str.
fn process_text<T: AsRef<str>>(text: T) {
    // as_ref() converts T to &str.
    // No allocation happens here.
    let slice: &str = text.as_ref();
    println!("Processing: {}", slice);
}

fn main() {
    // Pass a literal &str.
    process_text("Alice");
    
    // Pass a String. It borrows the String's data.
    let owned = String::from("Bob");
    process_text(&owned);
    
    // owned is still valid. We didn't move it.
    println!("Owned is still: {}", owned);
}

AsRef<str> is a trait bound. It says "T can be converted to a &str". String implements AsRef<str>. &str implements AsRef<str>. The function body works with &str. The caller decides the type. This is idiomatic for library APIs. It avoids duplication. It avoids unnecessary allocations when the caller has a String and just wants to read it.

Check the lifetime. If the owner dies, the slice dangles. The compiler will catch it before runtime.

Pitfalls and compiler signals

Rust's type system catches mistakes early. Here are the common errors when mixing String and &str.

E0308: mismatched types

This is the error you started with. You passed &str where String is expected, or vice versa. Rust doesn't coerce &str to String because that requires allocation. Rust doesn't coerce String to &str in all contexts because that requires borrowing. Fix it by converting explicitly with .to_string() or changing the signature to &str.

E0382: use of moved value

You passed a String to a function that takes String. The function took ownership. You tried to use the variable again.

fn take_name(name: String) {}

fn main() {
    let name = String::from("Alice");
    take_name(name);
    // println!("{}", name); // Error: E0382 use of moved value
}

The String moved into take_name. name is gone. Fix it by passing a reference &name if the function accepts &str, or clone the string .clone() if you need to keep the original.

E0597: borrowed value does not live long enough

You tried to return a &str that points to a String created inside the function.

fn get_name() -> &str {
    let name = String::from("Alice");
    &name // Error: E0597 borrowed value does not live long enough
}

name is a local variable. It drops when the function returns. The &str would point to freed memory. Rust forbids this. Fix it by returning a String instead, or borrow from an input parameter that lives longer.

E0507: cannot move out of borrowed content

You tried to extract a String from a borrowed struct or container.

struct Config {
    host: String,
}

fn get_host(config: &Config) -> String {
    config.host // Error: E0507 cannot move out of borrowed content
}

config is a reference. You can't move the String out of it. That would leave the struct in an invalid state. Fix it by cloning .clone() or returning a &str &config.host.

Read the error code. It tells you exactly what rule you broke. Fix the code to match the rule.

Decision matrix

Pick the type that matches the responsibility. Ownership costs memory. Borrowing costs lifetimes. Choose wisely.

Use String when you need to store the data in a struct or return it from a function. Use String when you need to mutate the text, like pushing characters or replacing substrings. Use String when the data must outlive the current scope and no other variable owns it.

Use &str when you only need to read the text. Use &str for function parameters that process input without storing it. Use &str for string literals and slices of owned strings. Use &str to avoid allocations in hot loops.

Use .to_string() when you have a &str and need to satisfy a String requirement. Use .to_string() sparingly; prefer changing the signature to &str if ownership isn't required.

Use AsRef<str> when writing a generic function that accepts both String and &str without duplicating code. Use AsRef<str> for library APIs where flexibility matters more than zero-cost abstraction.

Trust the borrow checker. It usually has a point.

Where to go next