Fix Rust error[E0308]: mismatched types โ€” String vs &str

beginner๐Ÿฆ€ Rust2026-04-15| Rust 1.x (all versions), any OS โ€” Linux, macOS, Windows

Error Message

error[E0308]: mismatched types: expected struct `String`, found `&str`
#rust#string#types#conversion

The Error

error[E0308]: mismatched types: expected struct `String`, found `&str`

Or the reverse:

error[E0308]: mismatched types: expected `&str`, found struct `String`

This is one of the first walls new Rust developers hit. String and &str look nearly identical in source code โ€” but to the compiler, they're completely different types. Rust won't silently convert between them.

Why This Happens

String is heap-allocated and owned โ€” it carries its data with it. &str is just a borrowed reference into some string data that lives elsewhere. Two different things. The compiler enforces this boundary strictly, so a mismatch blows up at compile time, not at runtime.

Common situations where this catches people:

  • Passing a string literal (&str) to a function that expects String
  • Passing an owned String where a function wants &str
  • Assigning a literal to a struct field typed as String
  • Returning a literal from a function with return type String

Quick Fix โ€” Choose Your Case

Case 1: You have &str, need String

Call .to_string(), .to_owned(), or String::from():

// Before โ€” compile error
fn greet(name: String) {
    println!("Hello, {}!", name);
}

fn main() {
    greet("Alice"); // error: expected String, found &str
}

// After โ€” fixed
fn main() {
    greet("Alice".to_string());
    // or
    greet("Alice".to_owned());
    // or
    greet(String::from("Alice"));
}

All three produce the same result. Most Rust code you'll see in the wild uses .to_string().

Case 2: You have String, need &str

Borrow it with & or call .as_str():

fn print_name(name: &str) {
    println!("{}", name);
}

fn main() {
    let name = String::from("Alice");

    print_name(&name);         // deref coercion: &String โ†’ &str
    print_name(name.as_str()); // explicit, same effect
    print_name(&name[..]);     // slice syntax โ€” works but rarely seen
}

&name works because Rust silently applies deref coercion from &String to &str. No copying happens. This is the idiomatic approach.

Case 3: Struct field typed as String, you're assigning a literal

struct Config {
    host: String,
}

// Error
let c = Config { host: "localhost" }; // expected String, found &str

// Fixed
let c = Config { host: "localhost".to_string() };
let c = Config { host: String::from("localhost") };

Case 4: Function return type is String but you're returning a literal

// Error
fn get_default() -> String {
    "unknown" // expected String, found &str
}

// Fixed
fn get_default() -> String {
    "unknown".to_string()
    // or: String::from("unknown")
}

Case 5: Pushing literals into a Vec

// Error
let mut names: Vec = Vec::new();
names.push("Alice"); // expected String, found &str

// Fixed
names.push("Alice".to_string());
names.push(String::from("Bob"));

Better Yet: Accept &str in Your Functions

If you control the function signature and don't need to store or modify the string, change the parameter type to &str instead of String. It accepts both literals and owned strings via coercion โ€” no conversions required at the call site:

// Restrictive โ€” callers must hand you an owned String
fn greet(name: String) { ... }

// Flexible โ€” works with literals and &String alike
fn greet(name: &str) { ... }

// Both of these now compile without .to_string():
greet("Alice");         // &str literal โ€” works
greet(&owned_string);   // &String โ€” works via coercion

The Rust API Guidelines recommend &str for read-only string parameters. It's a small change that saves callers from needless allocations.

Verify the Fix

Rebuild the project:

cargo build

A clean build looks like:

   Compiling myproject v0.1.0
    Finished dev [unoptimized + debuginfo] target(s) in 0.42s

No error[E0308] in the output means the type mismatch is gone. For a quick one-file check, rustc main.rs works too.

When to Use What

  • .to_string() โ€” the go-to for converting &str to String. Readable, idiomatic.
  • .to_owned() โ€” same result, works on any Borrow type. Some style guides prefer it.
  • String::from() โ€” explicit construction. Good when you want to be obvious about intent.
  • &string or .as_str() โ€” going from owned to borrowed. Zero-cost, no allocation.

Quick Reference

// &str โ†’ String
let s: String = "hello".to_string();
let s: String = "hello".to_owned();
let s: String = String::from("hello");
let s: String = format!("hello"); // use this when you need formatting too

// String โ†’ &str
let r: &str = &owned;          // deref coercion
let r: &str = owned.as_str();  // explicit
let r: &str = &owned[..];      // full slice

Related Error Notes