趣味学RUST基础篇(String)
String
:Rust 里的“魔法卷轴”!
在前面的内容,我们学会了用 Vec<T>
装整数、装枚举,像个收纳高手。
但作为冒险者,你总得写点日记吧?比如:
“今日击败恶龙,获得金币 ×100,太棒了”
这就需要一个能装文字的容器——在 Rust 里,它就是 String
!
但别被名字骗了,String
可不是简单的“一串字符”。它是个会魔法的卷轴,能装中文、阿拉伯文、俄文、emoji……\但正因为太强大,它也比你想象的复杂得多。接下来,我们揭开它的神秘面纱!
首先:什么是“字符串”?
在 Rust 世界里,有两种“文字”:
类型 | 名字 | 特点 |
---|---|---|
&str | 字符串切片 | “只读卷轴”,通常来自字面量,比如 "hello" |
String | 动态字符串 | “可编辑卷轴”,能增、删、改,像 Vec<T> 一样灵活 |
通常我们说“字符串”,就是指这两个一起。
其他语言可能把它们混为一谈,但 Rust 很认真:“只读”和“可变”是两回事!
创建一个“魔法卷轴”
方法一:空卷轴启动!
let mut s = String::new();
就像买了一张空白羊皮纸,等着你写点什么。
方法二:从文字变卷轴!
let data = "初始内容";
let s = data.to_string(); // 变!
或者更直接:
let s = "初始内容".to_string();
let s = String::from("初始内容"); // 效果一样!
to_string()
还是String::from()
?
随你喜欢!就像“番茄炒蛋”和“蛋炒番茄”,看你心情!
它支持所有语言!
String
是 UTF-8 编码的,所以你可以写:
let 你好 = String::from("你好");
let привет = String::from("Здравствуйте");
let 안녕 = String::from("안녕하세요");
let 😎 = String::from("今天真棒!");
统统支持!
更新字符串:往卷轴上写字!
1. 用 push_str
添加文字
let mut s = String::from("Hello");
s.push_str(", world!");
现在 s
是 "Hello, world!"
!
有趣的是:push_str
接收的是 &str
(字符串切片),所以它不拿走所有权:
let mut s1 = String::from("foo");
let s2 = "bar";
s1.push_str(s2);
println!("s2 还能用:{s2}"); // 正常打印!
Rust 很贴心,不会偷偷把你的字符串“吃掉”。
2. 用 push
添加单个字符
let mut s = String::from("lo");
s.push('l'); // 注意是单引号!
结果:"lol"
# 拼接字符串:把多个卷轴合成一个!
方法一:用 +
号
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2; // 注意:&s2 是引用
结果是 "Hello, world!"
。
但注意:s1
被“吃掉”了!
你不能再用 s1
,因为它已经被移动到 s3
里了。
原理:
+
其实调用了add(self, &str)
,它拿走了self
的所有权。
所以这行代码其实是:
- 拿走
s1
- 把
s2
的内容复制进来 - 返回一个全新的
String
虽然写起来像拼积木,但背后是“拆了旧的,建个新的”。
方法二:用 format!
宏 —— 推荐!
如果你要拼接多个字符串,+
就太难看了:
let s = s1 + "-" + &s2 + "-" + &s3; // 眼都花了!
这时,用 format!
宏,优雅又安全:
let s = format!("{}-{}-{}", s1, s2, s3);
不获取所有权、 代码清晰、 性能好, 就像 println!
,但不打印,而是返回一个 String
!
不能用索引?s[0]
为什么不行?
在 Python、JavaScript 里,你可能习惯这么写:
s = "hello"
print(s[0]) # 'h'
但在 Rust,这样写会编译失败!
let s = String::from("hello");
let h = s[0]; // 编译错误!
为什么?因为 String
在 Rust 里不是“字符数组”那么简单!
🔍 深入魔法:String
到底怎么存的?
String
本质是 Vec<u8>
—— 一串字节!
比如:
let hello = String::from("Hola");
它在内存里是:[72, 111, 108, 97]
—— 4 个字节。
但如果是:
let hello = String::from("Здравствуйте");
这 12 个西里尔字母,却占了 24 个字节!因为每个字母用 2 字节 UTF-8 编码。
所以,如果你写:
let s = &hello[0..1]; // 只取第一个字节?
你会得到一个不完整的字节序列,根本不是合法字符!
Rust 宁愿让你编译失败,也不让你写出“看似正确实则乱码”的代码。
三种看字符串的方式
Rust 让你明确选择:你想怎么“读”字符串?
方式 | 方法 | 例子(“नमस्ते”) |
---|---|---|
字节 | .bytes() | 18 个字节 [224, 164, ...] |
字符(Unicode 标量值) | .chars() | 6 个 char :'न', 'म', 'स', '्', 'त', 'े' |
字形簇(视觉上的“字”) | 需要外部库 | 4 个:“न”, “म”, “स्”, “ते” |
.chars()
最常用,但注意:像्
这种是“变音符号”,单独没意义。
遍历字符串:推荐方式
遍历每个字符:
for c in "नमस्ते".chars() {println!("{c}");
}
遍历每个字节:
for b in "नमस्ते".bytes() {println!("{b}");
}
想要“字形簇”?标准库没给,但你可以在 crates.io 找
unicode-segmentation
这样的库。
为什么 Rust 要这么复杂?
因为:
“简单”往往藏着陷阱。
其他语言让你用 s[0]
,但遇到中文、emoji 时,可能返回乱码或半个字符。
Rust 说:
“我宁可你现在觉得麻烦,也别将来在生产环境炸了服务器。”
所以它强制你思考:
- 我要的是字节?字符?还是视觉上的“字”?
- 我的代码是否真正理解 UTF-8?
#String
使用口诀
操作 | 推荐方式 |
---|---|
创建 | String::from("...") 或 "...".to_string() |
拼接 | format!("{} {}", s1, s2) (安全又清晰) |
添加 | push_str() (加字符串)、push() (加字符) |
遍历 | .chars() (最常用)或 .bytes() |
切片 | &s[0..4] (必须按字节,小心越界!) |
索引 | 不支持!用 .chars().nth(0) 代替(但性能差) |
总结:
字符串,远比你想象的复杂。
Rust 没有隐藏这种复杂性,而是把它摆在你面前,让你写出真正健壮的代码。