首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >09-Rust 教程 - 字符串

09-Rust 教程 - 字符串

作者头像
LarryLan
发布2026-04-13 14:58:51
发布2026-04-13 14:58:51
340
举报

字符串

字符串:String&str 的"双胞之谜"


🎬 引入

如果你学过其他语言,字符串不就是字符串吗?但在 Rust 里,字符串有两种String&str

新手第一次遇到这俩,基本是这样的:

代码语言:javascript
复制
fn main() {
    let s1 = "hello";
    let s2 = String::from("world");
    let s3 = s1 + s2;  // ❌ 编译错误!
}

编译器:"类型不匹配!" :"不都是字符串吗?!" 编译器:"一个是 &str,一个是 String,能一样吗?" :"......"

今天咱们就来彻底搞懂 Rust 的字符串,让你从此不再被编译器"教育"。


📌 核心概念

String vs &str:本质区别

特性

String

&str

类型

结构体(拥有所有权)

切片(引用)

内存

堆上分配

通常指向已有数据

可变性

可变

不可变

大小

动态增长

固定长度

字面量

需要转换

直接就是

生活化类比

  • String = 你自己买的笔记本,可以随便写、随便撕页
  • &str = 图书馆的书,只能看,不能改

字符串字面量:天生就是 &str

代码语言:javascript
复制
let s = "hello";  // 类型是 &str

字符串字面量(用双引号括起来的)天生就是 &str,它的类型是 &'static str——生命周期是 'static(跟程序一样长),因为内容存在二进制文件里。

创建 String:从 &str 转换

代码语言:javascript
复制
// 方法 1:from
let s1 = String::from("hello");

// 方法 2:to_string
let s2 = "hello".to_string();

// 方法 3:into(需要类型推断)
let s3: String = "hello".into();

推荐String::from() 最清晰。

UTF-8:Rust 字符串的"灵魂"

Rust 的字符串默认是 UTF-8 编码,这意味着:

  • 可以存任何语言的字符
  • 一个字符可能占 1-4 个字节
  • 不能通过索引访问(因为不知道第 N 个字符从哪开始)
代码语言:javascript
复制
let s = String::from("你好");

// ❌ 错误!不能索引
// let c = s[0];

// ✅ 正确:用 chars()
for c in s.chars() {
    println!("{}", c);
}

字符串操作:常用方法

代码语言:javascript
复制
let mut s = String::from("hello");

// 追加
s.push_str(" world");      // 追加字符串
s.push('!');               // 追加字符

// 拼接
let s1 = String::from("hello");
let s2 = String::from(" world");
let s3 = format!("{}{}", s1, s2);  // 推荐!
let s4 = &s1 + &s2;                // 也能用,但 s1 被移动

// 长度
let len = s.len();  // 字节数,不是字符数!

// 是否为空
if s.is_empty() {
    println!("空字符串");
}

// 截取(用切片)
let slice = &s[..];  // &str 类型

// 查找
if s.contains("world") {
    println!("包含 world");
}

// 替换
let replaced = s.replace("world", "Rust");

// 分割
for word in s.split_whitespace() {
    println!("{}", word);
}

格式化字符串:format!

代码语言:javascript
复制
let name = "Alice";
let age = ;

// 拼接字符串
let greeting = format!("你好,{}!你{}岁了。", name, age);

// 格式化数字
let pi = 3.1415926;
println!("π ≈ {:.2}", pi);  // 3.14

// 对齐
println!("{:>10}", "hello");   // 右对齐:"     hello"
println!("{:<10}", "hello");   // 左对齐:"hello     "
println!("{:^10}", "hello");   // 居中对齐:"  hello   "

字符串和数字转换

代码语言:javascript
复制
// 数字转字符串
let num = ;
let s = num.to_string();
let s = format!("{}", num);

// 字符串转数字
let s = "42";
let n: i32 = s.parse().expect("不是数字!");
let n: Result<i32, _> = s.parse();  // 不 panic,返回 Result

💻 代码示例

基础示例:字符串操作大全

代码语言:javascript
复制
fn main() {
    // 创建
    let mut s = String::from("Hello");
    
    // 追加
    s.push_str(", ");
    s.push('W');
    s.push_str("orld!");
    
    // 长度
    println!("长度:{} 字节", s.len());
    
    // 截取
    let hello = &s[..];
    println!("截取:{}", hello);
    
    // 查找
    if s.contains("World") {
        println!("包含 World");
    }
    
    // 替换
    let replaced = s.replace("World", "Rust");
    println!("替换后:{}", replaced);
    
    // 分割
    for word in s.split(|c| c == ',' || c == '!') {
        if !word.is_empty() {
            println!("单词:{}", word.trim());
        }
    }
}

错误示例 1:&str + String 类型不匹配

代码语言:javascript
复制
fn main() {
    let s1 = "hello";  // &str
    let s2 = String::from(" world");  // String
    let s3 = s1 + s2;  // ❌ 错误!
}

编译器错误

代码语言:javascript
复制
error[E0308]: mismatched types
 --> src/main.rs:4:17
  |
4 |     let s3 = s1 + s2;
  |                 ^^ expected `&str`, found struct `String`
  |
help: consider borrowing here
  |
4 |     let s3 = s1 + &s2;
  |                 +

人话翻译:编译器:"+ 运算符要求右边是 &str,你给了个 String。加个 & 借用一下啊!"

但还有问题:即使加了 &,左边是 &str 也不行。+ 的左边必须是 String

修复

代码语言:javascript
复制
let s3 = s1.to_string() + &s2;  // 把 s1 转成 String
// 或者
let s3 = format!("{}{}", s1, s2);  // 推荐!更清晰

错误示例 2:字符串索引

代码语言:javascript
复制
fn main() {
    let s = String::from("你好");
    let c = s[];  // ❌ 错误!
}

编译器错误

代码语言:javascript
复制
error[E0277]: the type `str` cannot be indexed by `{integer}`
 --> src/main.rs:3:14
  |
3 |     let c = s[0];
  |              ^^^ `str` cannot be indexed by `{integer}`

人话翻译:编译器:"str 不能用整数索引!UTF-8 编码下,你不知道第 0 个字符从哪开始啊!"

修复

代码语言:javascript
复制
let s = String::from("你好");

// 方法 1:用 chars()
let first = s.chars().next().unwrap();

// 方法 2:用切片(知道字节位置)
let slice = &s[..];  // "你" 占 3 个字节

错误示例 3:借用冲突

代码语言:javascript
复制
fn main() {
    let mut s = String::from("hello");
    let r = &s;
    s.push_str(" world");  // ❌ 错误!
    println!("{}", r);
}

编译器错误

代码语言:javascript
复制
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
 --> src/main.rs:4:5
  |
3 |     let r = &s;
  |             -- immutable borrow occurs here
4 |     s.push_str(" world");
  |     ^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
5 |     println!("{}", r);
  |                    - immutable borrow used here

人话翻译:编译器:"r 正在不可变借用 s,你又想可变借用(push_str 需要 &mut self)?不行!"

修复

代码语言:javascript
复制
let mut s = String::from("hello");
s.push_str(" world");
let r = &s;
println!("{}", r);

错误示例 4:len() 不是字符数

代码语言:javascript
复制
fn main() {
    let s = String::from("你好");
    println!("长度:{}", s.len());  // 输出:6,不是 2!
}

人话翻译len() 返回的是字节数,不是字符数。"你"和"好"各占 3 个字节(UTF-8),所以是 6。

修复

代码语言:javascript
复制
let char_count = s.chars().count();  // 2

🐛 常见坑点

坑点 1:混淆 String&str

代码语言:javascript
复制
fn print_name(name: String) {
    println!("{}", name);
}

fn main() {
    let name = "Alice";
    print_name(name);  // ❌ 错误!&str != String
}

修复:用 &str 更灵活。

代码语言:javascript
复制
fn print_name(name: &str) {
    println!("{}", name);
}

坑点 2:+ 运算符的陷阱

代码语言:javascript
复制
let s1 = String::from("hello");
let s2 = String::from(" world");
let s3 = s1 + &s2;  // s1 被移动,不能再用了
println!("{}", s1);  // ❌ 错误!

修复:用 format!

代码语言:javascript
复制
let s3 = format!("{}{}", s1, s2);

坑点 3:切片越界

代码语言:javascript
复制
let s = String::from("hello");
let slice = &s[..];  // panic!

修复:确保索引有效。

代码语言:javascript
复制
let slice = &s[..s.len()];

坑点 4:中文字符切片

代码语言:javascript
复制
let s = String::from("你好");
let slice = &s[..];  // panic!不是有效的 UTF-8 边界

修复:用 chars() 或确保在字符边界。

代码语言:javascript
复制
let slice = &s[..];  // "你" 占 3 字节

🎯 实战案例

案例 1:统计词频

代码语言:javascript
复制
use std::collections::HashMap;

fn count_words(text: &str) -> HashMap<String, usize> {
    let mut counts = HashMap::new();
    
    for word in text.split_whitespace() {
        let word = word.to_lowercase();
        let word = word.trim_matches(|c: char| !c.is_alphanumeric());
        
        *counts.entry(word).or_insert() += ;
    }
    
    counts
}

fn main() {
    let text = "hello world hello Rust world hello";
    let counts = count_words(text);
    
    for (word, count) in &counts {
        println!("{}: {}", word, count);
    }
}

案例 2:字符串反转

代码语言:javascript
复制
fn reverse(s: &str) -> String {
    s.chars().rev().collect()
}

fn main() {
    let s = "你好,Rust!";
    let reversed = reverse(s);
    println!("原字符串:{}", s);
    println!("反转后:{}", reversed);
}

案例 3:解析 CSV 行

代码语言:javascript
复制
fn parse_csv_line(line: &str) -> Vec<&str> {
    line.split(',').collect()
}

fn parse_csv_line_trimmed(line: &str) -> Vec<String> {
    line.split(',')
        .map(|s| s.trim().to_string())
        .collect()
}

fn main() {
    let line = "Alice, 25, Engineer";
    let fields = parse_csv_line(line);
    println!("{:?}", fields);
    
    let fields = parse_csv_line_trimmed(line);
    println!("{:?}", fields);
}

🧠 思维导图

09-字符串
09-字符串

📝 小结

  1. String 拥有所有权,&str 是引用——函数参数优先用 &str
  2. 字符串字面量是 &str,存在二进制里,生命周期 'static
  3. UTF-8 编码,不能用索引访问,用 chars() 遍历
  4. len() 是字节数,不是字符数,中文字符要注意
  5. 拼接用 format!+ 运算符会移动所有权

下篇预告:函数返回引用时,编译器总抱怨"生命周期"是啥?下一篇聊聊生命周期基础,揭开这个神秘概念的面纱!


🔗 参考资料

  • Rust Book - 字符串
  • Rust By Example - 字符串
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-04-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Larry的Hub 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 字符串
    • 🎬 引入
    • 📌 核心概念
      • String vs &str:本质区别
      • 字符串字面量:天生就是 &str
      • 创建 String:从 &str 转换
      • UTF-8:Rust 字符串的"灵魂"
      • 字符串操作:常用方法
      • 格式化字符串:format! 宏
      • 字符串和数字转换
    • 💻 代码示例
      • 基础示例:字符串操作大全
      • 错误示例 1:&str + String 类型不匹配
      • 错误示例 2:字符串索引
      • 错误示例 3:借用冲突
      • 错误示例 4:len() 不是字符数
    • 🐛 常见坑点
      • 坑点 1:混淆 String 和 &str
      • 坑点 2:+ 运算符的陷阱
      • 坑点 3:切片越界
      • 坑点 4:中文字符切片
    • 🎯 实战案例
      • 案例 1:统计词频
      • 案例 2:字符串反转
      • 案例 3:解析 CSV 行
    • 🧠 思维导图
    • 📝 小结
    • 🔗 参考资料
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档