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 expectsString - Passing an owned
Stringwhere 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&strtoString. Readable, idiomatic..to_owned()โ same result, works on anyBorrowtype. Some style guides prefer it.String::from()โ explicit construction. Good when you want to be obvious about intent.&stringor.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

