Question
Rust Raw String Literals: What r#"..."# Means and How Interpolation Works
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_jsonfor 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\nmeans 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.
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 forvar1
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!
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
| Concept | What it does | Supports interpolation? | Best use |
|---|---|---|---|
Normal string literal "..." | Stores text, escape sequences are processed | No | General strings |
Raw string literal r#"..."# | Stores text literally with minimal escaping | No | JSON snippets, regex, HTML, SQL |
format!(...) | Builds a new string from a template and values | Yes | Dynamic strings |
println!(...) | Formats and prints output | Yes | Console output |
serde_json::json!(...) | Builds structured JSON values | Yes, through Rust expressions | Real 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);
rawkeeps literal text onlyformattedinserts values
Manual JSON vs serde_json
| Approach | Pros | Cons |
|---|---|---|
Manual string with format! | Quick for tiny examples | Easy to break, escaping issues |
serde_json::json! | Safe, readable, valid JSON handling | Requires dependency |
For production code, serde_json is usually the better choice.
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 }.
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.
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.