Question

Rust Raw String Literals: What r#"..."# Means and How Interpolation Works

ruststringsyntaxstring-literals

Question

I saw the Rust syntax r#"..."#, but I could not find a clear explanation of what it does. It seemed useful for creating JSON.

let var1 = "test1";
let json = r#"{"type": "type1", "type2": var1}"#;
println!("{}", json); // => {"type": "type1", "type2": var1}

What is r#"..."# called in Rust, and what does it do? Also, how can I make var1 evaluate to its value inside the JSON string?

Short Answer

By the end of this page, you will understand Rust raw string literals, why they are useful for writing text like JSON without lots of escaping, and why variables such as var1 do not automatically evaluate inside them. You will also learn the usual Rust ways to insert values into strings, including format! and safer JSON-building approaches.

Concept

Rust's r#"..."# syntax is called a raw string literal.

A raw string literal tells Rust to treat most characters inside the string as plain text. This is especially useful when your string contains many double quotes, backslashes, or JSON-like content.

For example, a normal Rust string containing JSON often needs escaping:

let json = "{\"type\": \"type1\"}";

With a raw string literal, the same text is easier to read:

let json = r#"{"type": "type1"}"#;

Why it matters

In real code, raw strings are useful for:

  • JSON snippets
  • Regular expressions
  • File paths
  • SQL queries
  • HTML templates
  • Text that contains lots of quotes or backslashes

Important rule

A raw string literal does not perform variable interpolation.

That means this code:

let var1 = "test1";
let json = r#"{"type": "type1", "type2": var1}"#;

stores the exact text var1 inside the string. Rust does not replace it with the variable's value.

If you want to insert a variable into a string, you usually use:

  • format! for building strings
  • a JSON library such as serde_json for building real JSON safely

About the #

Rust lets you add one or more # characters around a raw string delimiter.

Examples:

let a = r"hello";
let b = r#"hello"#;
let c = r##"hello"##;

This helps when the text itself contains quotes or quote-like patterns. The string ends only when Rust sees the matching closing delimiter.

Mental Model

Think of a normal string as text inside a box where some characters have special meaning.

For example:

  • " means an escaped quote
  • \n means a newline
  • \\ means a backslash

A raw string is like telling Rust:

"Do not interpret the contents. Just copy the text as written until you reach the matching closing marker."

So r#"..."# is like putting a note on the box saying literal text only.

But raw strings are still just strings. They are not templates. They do not scan for variable names and replace them.

So if you write:

r#"hello var1"#

Rust sees exactly the characters h e l l o space v a r 1.

Take Quiz

Syntax and Examples

Basic syntax

let s1 = "normal string";
let s2 = r"raw string";
let s3 = r#"raw string with \"quotes\" inside"#;
let s4 = r##"raw string that can contain "# inside"##;

Normal string vs raw string

fn main() {
    let normal = "{\"name\": \"Alice\"}";
    let raw = r#"{"name": "Alice"}"#;

    println!("{}", normal);
    println!("{}", raw);
}

Both print the same output:

{"name": "Alice"}

The difference is readability in source code.

Inserting variables with format!

fn main() {
    let var1 = "test1";
    let json = format!(r#"{{"type": "type1", "type2": "{}"}}"#, var1);

    println!("{}", json);
}

Output:

{"type": "type1", "type2": "test1"}

Why the doubled braces?

Inside format!, curly braces have special meaning because they mark placeholders like {}. To include literal { and } in the result, you must write them as {{ and }}.

Better way for real JSON: serde_json

use serde_json::json;

fn main() {
    let var1 = "test1";

    let value = json!({
        "type": "type1",
        "type2": var1
    });

    println!("{}", value);
}

Output:

{"type":"type1","type2":"test1"}

This is usually the safest and most maintainable approach when you are creating real JSON.

Step by Step Execution

Consider this example:

fn main() {
    let var1 = "test1";
    let json = format!(r#"{{"type": "type1", "type2": "{}"}}"#, var1);
    println!("{}", json);
}

Step by step

1. Create a variable

let var1 = "test1";

var1 points to the string slice "test1".

2. Call format!

let json = format!(r#"{{"type": "type1", "type2": "{}"}}"#, var1);

Rust processes the format string.

The raw string content is:

{{"type": "type1", "type2": "{}"}}

Because it is a raw string:

  • the double quotes do not need escaping for the Rust parser
  • the text is easier to read

3. format! reads the placeholders

Inside format!:

  • {{ becomes a literal {
  • }} becomes a literal }
  • {} becomes a placeholder for var1

4. Replace the placeholder

{} is replaced with var1, which is test1.

5. Final string value

json becomes:

{"type": "type1", "type2": "test1"}

6. Print the result

println!("{}", json);

This prints the completed JSON string.

What happens in the original code?

let json = r#"{"type": "type1", "type2": var1}"#;

Rust does not look up var1. It stores the exact characters v, a, r, 1 in the string.

Real World Use Cases

When raw strings are useful

JSON samples

When you want to embed a fixed JSON example in code:

let sample = r#"{"status": "ok", "count": 3}"#;

Regular expressions

Regex patterns often contain many backslashes:

let pattern = r#"\d+\.\d+"#;

Windows file paths

let path = r#"C:\Users\Alice\Documents"#;

SQL queries

let query = r#"
SELECT id, name
FROM users
WHERE active = true
"#;

HTML or template text

let html = r#"<div class="card">Hello</div>"#;

When variable insertion is needed

In real applications, you often need dynamic values:

  • building log messages
  • creating API payloads
  • generating file contents
  • assembling SQL queries carefully
  • composing command strings

In those cases, a raw string alone is not enough. You combine it with tools like:

  • format!
  • println!
  • templating libraries
  • structured builders like serde_json::json!
Take Quiz

Real Codebase Usage

In real Rust projects, developers usually separate literal text from dynamic data.

Common patterns

1. Use raw strings for readable static text

let template = r#"{"type": "fixed"}"#;

Good when the content never changes.

2. Use format! for small dynamic strings

let user = "alice";
let msg = format!(r#"User "{}" logged in"#, user);

Good for simple string assembly.

3. Use serde_json for JSON payloads

use serde_json::json;

let payload = json!({
    "user": "alice",
    "active": true
});

This avoids broken JSON and handles escaping correctly.

4. Use guard clauses before formatting

fn make_name_json(name: &str) -> Option<String> {
    if name.is_empty() {
        return None;
    }

    Some(format!(r#"{{"name": "{}"}}"#, name))
}

This pattern validates data early.

5. Prefer structured data over hand-built strings

In larger codebases, developers often define Rust structs and serialize them:

use serde::Serialize;

#[derive(Serialize)]
struct Payload<'a> {
    r#type: &'a str,
    type2: &'a str,
}

Then serialize safely instead of manually writing JSON text.

Why this matters

Manual string building is easy to get wrong. Real codebases prefer approaches that:

  • reduce escaping errors
  • prevent invalid JSON
  • make refactoring easier
  • improve readability

Common Mistakes

1. Expecting interpolation inside a string literal

Broken code:

let name = "Alice";
let s = r#"Hello, name"#;

This produces the literal text Hello, name.

Fix

Use format!:

let name = "Alice";
let s = format!("Hello, {}", name);

2. Confusing raw strings with formatted strings

Broken idea:

let value = 42;
let s = r#"Value: {}"#;

A raw string does not replace {} by itself.

Fix

Wrap it in format! or another formatting macro:

let value = 42;
let s = format!(r#"Value: {}"#, value);

3. Forgetting to escape braces in format!

Broken code:

let name = "Alice";
let json = format!(r#"{"name": "{}"}"#, name);

This is a problem because { and } are special to format!.

Fix

Double the braces:

let name = "Alice";
let json = format!(r#"{{"name": "{}"}}"#, name);

4. Building JSON manually when values may need escaping

Broken approach:

let name = "Alice \"Admin\"";
let json = format!(r#"{{"name": "{}"}}"#, name);

This can create invalid JSON if the string contains quotes or other special characters.

Better fix

Use serde_json:

use serde_json::json;

let name = "Alice \"Admin\"";
let json = json!({ "name": name });

5. Using the wrong number of # characters

If your text contains the closing delimiter, the raw string may end too early.

Fix

Add more # markers:

let s = r##"This text contains "# so one # is not enough"##;

Comparisons

ConceptWhat it doesSupports interpolation?Best use
Normal string literal "..."Stores text, escape sequences are processedNoGeneral strings
Raw string literal r#"..."#Stores text literally with minimal escapingNoJSON snippets, regex, HTML, SQL
format!(...)Builds a new string from a template and valuesYesDynamic strings
println!(...)Formats and prints outputYesConsole output
serde_json::json!(...)Builds structured JSON valuesYes, through Rust expressionsReal JSON generation

Raw string vs normal string

let normal = "{\"key\": \"value\"}";
let raw = r#"{"key": "value"}"#;

Use a raw string when readability matters and you want fewer escapes.

Raw string vs format!

let name = "Sam";
let raw = r#"Hello, name"#;
let formatted = format!("Hello, {}", name);
  • raw keeps literal text only
  • formatted inserts values

Manual JSON vs serde_json

ApproachProsCons
Manual string with format!Quick for tiny examplesEasy to break, escaping issues
serde_json::json!Safe, readable, valid JSON handlingRequires dependency

For production code, serde_json is usually the better choice.

Take Quiz

Cheat Sheet

Raw string literal syntax

r"text"
r#"text"#
r##"text"##

Rules

  • Raw strings store text more literally than normal strings.
  • Backslashes are not treated as escapes in the usual way.
  • Quotes inside the string are easier to include.
  • The string ends only at the matching closing delimiter.
  • More # characters let you include more complex content.

No interpolation

let name = "Alice";
let s = r#"Hello, name"#; // literal text

To insert values:

let s = format!("Hello, {}", name);

JSON with format!

let value = "test1";
let json = format!(r#"{{"type2": "{}"}}"#, value);

Remember:

  • {{ means literal {
  • }} means literal }

Safer JSON

use serde_json::json;

let value = "test1";
let obj = json!({ "type2": value });

Quick choice guide

  • Static text with many quotes: use a raw string
  • Dynamic text: use format!
  • Real JSON data: use serde_json

FAQ

What is r#"..."# called in Rust?

It is called a raw string literal.

Does a raw string evaluate variables automatically?

No. Rust string literals do not perform variable interpolation.

Why use # in a raw string?

The # helps Rust know where the raw string ends, especially when the content contains quotes.

What is the difference between r"..." and r#"..."#?

Both are raw strings. The # version is useful when the content includes double quotes or patterns that would otherwise end the string too early.

How do I insert a variable into a Rust string?

Use formatting macros such as format!, println!, or write!.

What is the best way to create JSON in Rust?

For real applications, use serde_json or serialize a struct with serde instead of building JSON manually.

Can I use raw strings with format!?

Yes. A raw string can be the format string passed to format!, but formatting still follows format! rules, including doubled braces for literal { and }.

Take Quiz

Mini Project

Description

Build a small Rust program that creates a JSON message describing a user action. This project shows the difference between writing fixed JSON text, formatting a string with variable values, and building JSON safely with serde_json.

Goal

Create a program that outputs a JSON payload containing dynamic Rust variable values.

Requirements

  • Create variables for a username and an action.
  • Build one JSON string using format!.
  • Build the same JSON payload using serde_json::json!.
  • Print both results.
  • Make sure the username and action appear as actual values, not as literal variable names.
Take Quiz

Keep learning

Related questions

View more