Rust字符串

字符串

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

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

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

fn main() {
    let s1 = "hello";
    let s2 = String::from("world");
    let s3 = s1 + s2;  // ❌ 编译错误!
}

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

String vs &str:本质区别

特性 String &str
类型 结构体(拥有所有权) 切片(引用)
内存 堆上分配 通常指向已有数据
可变性 可变 不可变
大小 动态增长 固定长度
字面量 需要转换 直接就是

生活化类比:

  • String = 你自己买的笔记本,可以随便写、随便撕页

  • &str = 图书馆的书,只能看,不能改

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

let s = "hello";  // 类型是 &str

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

创建 String:从 &str 转换

// 方法 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 个字符从哪开始)

let s = String::from("你好");

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

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

字符串操作:常用方法

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[0..5];  // &str 类型

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

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

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

格式化字符串:format! 宏

let name = "Alice";
let age = 25;

// 拼接字符串
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   "

字符串和数字转换

// 数字转字符串
let num = 42;
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

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

fn main() {
    // 创建
    let mut s = String::from("Hello");

    // 追加
    s.push_str(", ");
    s.push('W');
    s.push_str("orld!");

    // 长度
    println!("长度:{} 字节", s.len());

    // 截取
    let hello = &s[0..5];
    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 类型不匹配

fn main() {
    let s1 = "hello";  // &str
    let s2 = String::from(" world");  // String
    let s3 = s1 + s2;  // ❌ 错误!
}

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

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

修复:

let s3 = s1.to_string() + &s2;  // 把 s1 转成 String
// 或者
let s3 = format!("{}{}", s1, s2);  // 推荐!更清晰

错误示例 2:字符串索引

fn main() {
    let s = String::from("你好");
    let c = s[0];  // ❌ 错误!
}

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

修复:

let s = String::from("你好");

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

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

错误示例 3:借用冲突

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

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

修复:

let mut s = String::from("hello");
s.push_str(" world");
let r = &s;
println!("{}", r);

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

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

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

修复:

let char_count = s.chars().count();  // 2

常见坑点

坑点 1:混淆 String 和 &str

fn print_name(name: String) {
    println!("{}", name);
}

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

修复:用 &str 更灵活。

fn print_name(name: &str) {
    println!("{}", name);
}

坑点 2:+ 运算符的陷阱

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

修复:用 format!。

let s3 = format!("{}{}", s1, s2);

坑点 3:切片越界

let s = String::from("hello");
let slice = &s[0..100];  // panic!

修复:确保索引有效。

let slice = &s[0..s.len()];

坑点 4:中文字符切片

let s = String::from("你好");
let slice = &s[0..1];  // panic!不是有效的 UTF-8 边界

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

let slice = &s[0..3];  // "你" 占 3 字节

文档信息

版权声明:可自由转载(请注明转载出处)-非商用-非衍生

发表时间:2026年4月17日 09:15