Question

How to Match a String in Rust: String vs &str in match Expressions

ruststringmatch

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.

  • String is an owned, growable string stored on the heap.
  • &str is 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 String owns the text.
  • A &str borrows 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.

Take Quiz

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:

  1. String::from("b") creates an owned String containing b.
  2. stringthing.as_str() creates a borrowed &str view into that string.
  3. match compares that &str against each pattern in order.
  4. "a" does not match.
  5. "b" matches.
  6. Rust runs println!("1").
  7. The remaining arms are skipped.

Trace table

StepExpressionTypeResult
1String::from("b")Stringowned string created
2stringthing.as_str()&strborrowed string slice
3compare with "a"&str patternno match
4compare with "b"&str patternmatch
5run 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.

Take Quiz

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

  • value is a String
  • "a" is a &str
  • match patterns 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

TypeOwnershipGrowableTypical use
StringOwnedYesStore and build text
&strBorrowedNoRead-only view of text

match vs if/else for strings

ApproachBest forExample
matchMany exact known valuescommands, modes, categories
if/elseOne or two checks, more flexible conditionssimple equality or combined logic

as_str() vs &string[..]

FormMeaningRecommended
string.as_str()Borrow the whole String as &strYes, clearer
&string[..]Slice the whole string as &strValid, but less explicit

Matching strings vs using enums

ApproachBest forTradeoff
Match on &strInput parsing, quick branchingLess type-safe
Match on enumInternal program states, fixed categoriesRequires 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.

Take Quiz

Cheat Sheet

Quick rules

  • String is owned text
  • &str is a borrowed string slice
  • String literals like "abc" are &str
  • In match, patterns must match the value's type
  • To match a String against 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.

Take Quiz

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 match to compare the command against string literals
  • Handle at least three known commands
  • Include a fallback case for unknown commands
  • Keep the original String usable after the match
Take Quiz

Keep learning

Related questions

View more