Question
In Rust, how can you correctly match a String value against string literals inside a match expression?
For example, this code does not compile:
fn main() {
let stringthing = String::from("c");
match stringthing {
"a" => println!("0"),
"b" => println!("1"),
"c" => println!("2"),
}
}
It produces a type mismatch error because the match expression is working with a String, but the match arms use string literals of type &'static str.
Trying to match with String::from(...) patterns also does not work:
fn main() {
let stringthing = String::from("c");
match stringthing {
String::from("a") => println!("0"),
String::from("b") => println!("1"),
String::from("c") => println!("2"),
}
}
This fails because String::from(...) is a function call, not a valid match pattern.
What is the correct way to match a String against string literals in Rust?
Short Answer
By the end of this page, you will understand why String and &str are different types in Rust, why string literals work differently in match, and how to correctly match a String by converting or borrowing it as &str. You will also see common patterns used in real Rust code.
Concept
In Rust, String and &str are related but different types.
Stringis an owned, growable string stored on the heap.&stris a borrowed string slice, which is a view into string data.- String literals like
"hello"have type&'static str.
This matters in match because match patterns must match the type of the value being matched.
When you write:
match stringthing {
"a" => println!("0"),
// ...
}
Rust expects each pattern to match a String, because stringthing is a String. But "a" is a &str, not a String, so the types do not match.
Your second attempt fails for a different reason: patterns in match are not general expressions. String::from("a") creates a value at runtime, but match patterns must follow pattern syntax, not arbitrary function calls.
The usual solution is to match on a borrowed string slice instead of the owned String. Rust provides ways to view a String as &str, such as:
stringthing.as_str()
or
&stringthing[..]
Once you match on &str, string literals work naturally.
This concept matters a lot in real Rust programming because many APIs accept &str rather than String. Borrowing avoids unnecessary allocation and lets you compare, match, and pass strings efficiently.
Mental Model
Think of String as a full book you own, and &str as a page reference pointing to text inside that book.
- A
Stringowns the text. - A
&strborrows access to some or all of that text. - A string literal like
"a"is already a borrowed piece of text.
match wants the thing on the left and the patterns on the right to speak the same language.
If you try to compare:
- owned book (
String) - page reference (
&str)
Rust says: these are not the same type.
So the fix is simple: borrow the String as &str, then match it against string literals, which are also &str.
Syntax and Examples
The most common way to match a String against literals is to convert it to &str first.
Recommended syntax
fn main() {
let stringthing = String::from("c");
match stringthing.as_str() {
"a" => println!("0"),
"b" => println!("1"),
"c" => println!("2"),
_ => println!("other"),
}
}
Why this works
stringthing.as_str()gives a&str"a","b", and"c"are also&str- Now the matched value and the patterns have compatible types
Another valid form
fn main() {
let stringthing = String::from("c");
match &stringthing[..] {
"a" => println!("0"),
"b" => println!("1"),
"c" => println!("2"),
_ => println!("other"),
}
}
This also borrows the whole String as a string slice.
Matching without taking ownership
If you still need the original String after matching, borrowing is especially useful:
fn main() {
let stringthing = String::from("hello");
match stringthing.as_str() {
"hello" => println!("greeting"),
_ => println!("unknown"),
}
println!("Still have the String: {}", stringthing);
}
Because as_str() only borrows, stringthing is still available afterward.
Simple comparison as an alternative
If you only need one condition, if may be simpler than match:
fn main() {
let stringthing = String::from("c");
if stringthing == "c" {
println!("2");
}
}
Rust can compare String and &str in equality checks, but match patterns are stricter about types.
Step by Step Execution
Consider this example:
fn main() {
let stringthing = String::from("b");
match stringthing.as_str() {
"a" => println!("0"),
"b" => println!("1"),
"c" => println!("2"),
_ => println!("other"),
}
}
Here is what happens step by step:
String::from("b")creates an ownedStringcontainingb.stringthing.as_str()creates a borrowed&strview into that string.matchcompares that&stragainst each pattern in order."a"does not match."b"matches.- Rust runs
println!("1"). - The remaining arms are skipped.
Trace table
| Step | Expression | Type | Result |
|---|---|---|---|
| 1 | String::from("b") | String | owned string created |
| 2 | stringthing.as_str() | &str | borrowed string slice |
| 3 | compare with "a" | &str pattern | no match |
| 4 | compare with "b" | &str pattern | match |
| 5 | run arm body | - | prints 1 |
Important detail
The match does not build new String values for comparison. It compares the borrowed string slice to borrowed string literal patterns.
Real World Use Cases
Matching strings is common in Rust programs whenever text input determines behavior.
Command-line tools
fn handle_command(command: String) {
match command.as_str() {
"start" => println!("Starting..."),
"stop" => println!("Stopping..."),
"status" => println!("Checking status..."),
_ => println!("Unknown command"),
}
}
Parsing user input
A menu selection entered as text can be matched against known options.
API routing or action selection
A small internal service might use string-based action names like "create", "update", or "delete".
Configuration values
fn set_mode(mode: String) {
match mode.as_str() {
"debug" => println!("Debug mode enabled"),
"release" => println!("Release mode enabled"),
_ => println!("Invalid mode"),
}
}
Data cleaning and normalization
After reading text from a file, CSV, or environment variable, code often matches known string values to decide what to do next.
Real Codebase Usage
In real Rust projects, developers usually avoid taking ownership unless needed. That means they often work with &str for matching.
Common pattern: accept &str in functions
fn handle_status(status: &str) {
match status {
"ok" => println!("All good"),
"error" => println!("Something went wrong"),
_ => println!("Unknown status"),
}
}
Then you can call it with either a string literal or a String:
fn main() {
let s = String::from("ok");
handle_status(&s);
handle_status("error");
}
Guard clauses and validation
fn parse_role(role: &str) {
match role {
"admin" => println!("Full access"),
"user" => println!("Limited access"),
"guest" => println!("Read only"),
_ => println!("Invalid role"),
}
}
This is a common validation pattern for config values, request fields, and CLI arguments.
Match after trimming or normalizing
fn main() {
let input = String::from(" yes ");
match input.trim() {
"yes" => println!("confirmed"),
"no" => println!("declined"),
_ => println!("invalid input"),
}
}
Methods like trim() also return &str, which fits naturally with string literal matching.
Prefer enums when possible
In larger codebases, developers often convert known string values into enums first. This makes the rest of the code safer and clearer. But for simple input matching, match on &str is very common.
Common Mistakes
1. Matching a String directly against string literals
This does not work:
let value = String::from("a");
match value {
"a" => println!("A"),
_ => println!("Other"),
}
Why
valueis aString"a"is a&strmatchpatterns require compatible types
Fix
match value.as_str() {
"a" => println!("A"),
_ => println!("Other"),
}
2. Using function calls as patterns
This is invalid:
match value {
String::from("a") => println!("A"),
_ => println!("Other"),
}
Why
String::from("a") is an expression, not a pattern. Match arms must use pattern syntax.
3. Forgetting the wildcard arm
If you do not handle all possible cases, Rust may reject the match as non-exhaustive.
match value.as_str() {
"a" => println!("A"),
"b" => println!("B"),
}
Fix
match value.as_str() {
"a" => println!("A"),
"b" => println!("B"),
_ => println!("Other"),
}
4. Moving the String when you only need to borrow it
If you write code that consumes the string unnecessarily, you may not be able to use it later.
Preferred approach:
let value = String::from("hello");
match value.as_str() {
"hello" => println!("Hi"),
_ => println!("Other"),
}
println!("{}", value);
5. Using match when if is simpler
If there is only one comparison, this may be clearer:
if value == "hello" {
println!("Hi");
}
Choose match when you have multiple known cases.
Comparisons
String vs &str
| Type | Ownership | Growable | Typical use |
|---|---|---|---|
String | Owned | Yes | Store and build text |
&str | Borrowed | No | Read-only view of text |
match vs if/else for strings
| Approach | Best for | Example |
|---|---|---|
match | Many exact known values | commands, modes, categories |
if/else | One or two checks, more flexible conditions | simple equality or combined logic |
as_str() vs &string[..]
| Form | Meaning | Recommended |
|---|---|---|
string.as_str() | Borrow the whole String as &str | Yes, clearer |
&string[..] | Slice the whole string as &str | Valid, but less explicit |
Matching strings vs using enums
| Approach | Best for | Tradeoff |
|---|---|---|
Match on &str | Input parsing, quick branching | Less type-safe |
| Match on enum | Internal program states, fixed categories | Requires parsing step first |
If your program only accepts a small set of known values repeatedly, converting to an enum is often a better long-term design.
Cheat Sheet
Quick rules
Stringis owned text&stris a borrowed string slice- String literals like
"abc"are&str - In
match, patterns must match the value's type - To match a
Stringagainst literals, convert it to&str
Most common solution
match my_string.as_str() {
"a" => println!("A"),
"b" => println!("B"),
_ => println!("Other"),
}
Alternative
match &my_string[..] {
"a" => println!("A"),
_ => println!("Other"),
}
Not valid
match my_string {
"a" => println!("A"),
_ => println!("Other"),
}
match my_string {
String::from("a") => println!("A"),
_ => println!("Other"),
}
Equality check
if my_string == "a" {
println!("A");
}
Good design tip
Prefer &str in function parameters when you only need to read a string:
fn process(value: &str) {
match value {
"x" => println!("X"),
_ => println!("Other"),
}
}
FAQ
Why can't I match a String directly with "text" in Rust?
Because String and &str are different types. A string literal like "text" is a &str, so you need to borrow the String as &str first.
What is the easiest way to convert String to &str?
Use .as_str():
my_string.as_str()
Why does String::from("a") not work in a match arm?
Because match arms use patterns, not arbitrary expressions. String::from("a") is a function call expression.
Should I use match or if for strings in Rust?
Use match for multiple exact cases. Use if for one simple comparison or more complex boolean conditions.
Can I still use the String after matching with as_str()?
Yes. as_str() only borrows the string, so ownership stays with the original String.
Is &string[..] the same as string.as_str()?
For this use case, yes. Both produce a &str for the full string. as_str() is usually clearer.
Is matching strings common in Rust?
Yes, especially for parsing commands, configuration values, and user input. In larger programs, developers often convert strings into enums after parsing.
Mini Project
Description
Build a small command interpreter that reads a command stored in a String and responds based on its value. This demonstrates the practical way to match String input against string literals by borrowing it as &str first.
Goal
Create a Rust program that matches String commands like start, stop, and status and prints the correct response.
Requirements
- Store a command in a
String - Use
matchto compare the command against string literals - Handle at least three known commands
- Include a fallback case for unknown commands
- Keep the original
Stringusable after the match
Keep learning
Related questions
Absolute Values in Rust: Using the abs() Method
Learn how to calculate absolute values in modern Rust with abs(), handle signed integer overflow, and choose safer alternatives.
Accepting Iterators in Rust Functions with impl Iterator
Learn how to write Rust functions that accept HashMap iterators using generic bounds or impl Iterator, avoiding trait object and lifetime errors.
Access Enum Variant Values in Rust with Pattern Matching
Learn how to extract and print values stored in Rust enum variants using match and if let pattern matching, with practical examples.