什么是 Rust 中的 r#""#?魔法操作符全面解析

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

隐藏在神秘的 r#""# 背后的是什么?带代码的完整解释



你是否曾在 Rust 代码中遇到过像 r#"..."# 这样奇怪的写法,并心想:“这是什么魔法操作符?这是某种只为精英准备的秘密语法吗?”——你并不孤单。成千上万的开发者在 Stack Overflow 上问过这个问题,今天我们将一劳永逸地弄清楚它是什么,为什么需要它,以及如何在 Rust 中使用 原始字符串字面量(raw string literals)。



在本文中,我们不会仅仅给出一个干巴巴的定义。我们将从问题到解决方案,编写真实代码,看看 r#""# 如何让你免于头痛,并了解这种语法还能用在哪些地方。开始吧!



问题:当普通字符串破坏代码时



想象一下,你正在编写一个 JSON 解析器或正则表达式。在字符串内部,你需要使用引号和反斜杠。在普通的 Rust 代码中,这看起来像一场噩梦:



let json_string = "{\\"name\\": \\"Rust\\", \\"year\\": 2015}";let regex = "\\\\d{2}-\\\\d{2}-\\\\d{4}"; // 日期格式 DD-MM-YYYY


看到了吗?每次你需要在字符串内部使用引号时,你都要在前面加一个反斜杠(\\")。而如果你需要反斜杠本身,就得写 \\\\。代码很快就变成了一堆反斜杠的“阶梯”,难以阅读和调试。



但这还只是小菜一碟。如果你需要在字符串中插入一个制表符呢?那你就得写 \\t。而如果你需要的是字面上的文本 \\t 呢?那就得转义:\\\\t。写到第二行时,你的眼睛就开始疼了。



解决方案:原始字符串 —— r#""#



Rust 提供了一个优雅的解决方案——原始字符串字面量。它们以 r 为前缀,并用 # 包裹:



// 原始字符串:内部所有内容都被视为字面文本let raw_json = r#"{"name": "Rust", "year": 2015}"#;let raw_regex = r#"\\d{2}-\\d{2}-\\d{4}"#;


感受到区别了吗?无需转义!内部的引号就是普通的引号。反斜杠就是普通的反斜杠。Rust 编译器将 r#" 和 "# 之间的内容视为纯文本,不会尝试解释任何转义序列。



这在处理以下内容时特别有用:

  • 正则表达式 —— 它们充满了反斜杠和特殊字符。
  • JSON、XML、HTML —— 包含大量引号的字符串。
  • SQL 查询 —— 尤其是带有嵌套引号的情况。
  • Windows 文件路径 —— 用 C:\\Users\\Name 代替 C:\\\\Users\\\\Name。
  • 任何需要保留反斜杠原样的数据。


它在底层是如何工作的?



当 Rust 遇到 r#"..."# 时,它只是将起始 r#" 和结束 "# 之间的所有内容原样复制到字符串中,不做任何处理。没有 会被转换为换行符,没有 \\t 会变成制表符。这是文本的字面切片。



但有一个细微之处:如果字符串内部本身出现了 "# 序列怎么办?例如,你想插入文本 #"hello"#。如果你写成 r#"#"hello"#"#,编译器会感到困惑——字面量在哪里结束?



为此,Rust 设计了多重哈希。你可以使用 r##"..."##、r###"..."### 等等。两边的 # 数量必须匹配:



// 字符串内部包含 "# —— 使用双哈希let tricky = r##"包含 "# 的字符串"##;println!("{}", tricky); // 输出:包含 "# 的字符串

// 如果内部有 "## 呢?那就用三重哈希:let very_tricky = r###"包含 "## 的文本"###;

博客

书籍推荐