Rust: String vs &str
首发于:简书
当你开始第一次学习Rust的时候,不知不觉中就会开始对string类型感到困惑,并与编译器斗智斗勇:),通常你会认为那应该是一个string吧,然后编译器就说: Shut the fu*k up。 (努力保持微笑??
为了帮读者弄清楚Rust中String, &String, str 和 &str的区别和联系,花了一点时间帮你们翻译了一篇文章并努力让它看起来不那么无聊 ??。(不用谢我??,觉得有用的话点个赞叭,谢谢啦~
首先,我们来看一个炒鸡简单的函数:向老铁问好!
1 2 3 4 5 6 7 8 | fn main() { let friend_name = "laotie"; greet(friend_name); } fn greet(name: String) { println!("{}!, what's up", name); } |
如果你尝试编译这段代码,编译器就会教你做人(大雾
来看看错误信息叭
1 2 3 4 5 6 7 8 9 10 11 12 | error[E0308]: mismatched types --> src/main.rs:3:9 | 3 | greet(friend_name); | ^^^^^^^^^^^ | | | expected struct `std::string::String`, found `&str` | help: try using a conversion method: `friend_name.to_string()` error: aborting due to previous error For more information about this error, try `rustc --explain E0308`. |
你可以在Rust-playground中运行这段代码,点"Run"就可以啦。
这里的错误信息还是很容易看懂的,
同时,它也引出了下面几个问题:
- 这段代码的背后发生了什么?
- 什么是
&str ? - 为什么使用函数
to_string() 来进行显式转换?
理解String 类型
要想回答这些问题,最好还是要理解Rust是如何将数据存储在内存中的,可以先去看看官方出品的Rust-Book。
如果你已经安装了Rust,可以在终端或者Powershell中输入:
rustup doc --book ,然后浏览器就会自动打开那本书了,俗称Rust中的"圣经"。
继续沿用前面的例子,我们来研究一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 缓冲区(buffer) / 容量(capacity) / / 长度(length) / / / +–––+–––+–––+ 堆栈框架 │ ? │ 8 │ 6 │ <- friend_name: String +–│–+–––+–––+ │ [–│–––––––––– 容量 ––––––––––––––] │ +–V–+–––+–––+–––+–––+–––+–––+–––+ 堆 │ l │ a │ o │ t │ i │ e │ │ │ +–––+–––+–––+–––+–––+–––+–––+–––+ [–––––––– 长度 –––––––––] |
Rust会将
看到这里你可能会有疑惑,
1 2 | let mut friend_name = "laotie" friend_name.push_str(" shuang ji 666"); |
事实上,如果你已经非常了解Rust的
总结一下:
理解字符串切片(str)
字符串切片(
如果我们只对名字最后的“双击666”感兴趣,我们可以用如下方法得到部分字符串:
1 2 3 4 | let mut friend_name = "laotie".to_string(); my_name.push_str( " shuang ji 666"); let last_text = &my_name[7..]; |
1 2 3 4 5 6 7 8 9 10 11 12 13 | friend_name: String last_text: &str [––––––––––––] [–––––––] +–––+––––+––––+–––+–––+–––+ stack frame │ ? │ 32 │ 20 │ │ ? │13 │ +–│–+––––+––––+–––+–│–+–––+ │ │ │ +–––––––––+ │ │ │ │ │ [–│––––––––––––––––––––– str –––––––––––––––––––––––] +–V–+–––+–––+–––+–––+–––+–––+–V–+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+ heap │ l │ a │ o │ t │ i │ e │ │ s │ h │ u │ a │ n │ g │ │ j │ i │ │ 6 │ 6 │ 6 │ +–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+ |
注意到
那么,
我想,这大概就解释清楚了
理解字符串字面量
看完上面那些,我想你大概已经有个感觉了,现在,我们要回答最核心的问题,即
回顾上面所讲的,如果我们要使用字符串切片
1 | let text = "I love Rust" //这是&str,不是String |
接下来的问题是,如果说
结论是字符串字面量有一点特殊,它们是“预分配文本(preallocated text)”的字符串切片的引用,该文本作为可执行文件的一部分存储在只读(read-only)内存中。换句话说,它是我们程序中附带的“内存”,不依赖堆分配的缓冲区。
这就是说,在执行程序时,堆栈上仍然有一项指向该预分配的内存(preallocated memory):
1 2 3 4 5 6 7 8 9 10 11 | my_name: &str [–––––––––––] +–––+–––+ stack frame │ ? │ 6 │ +–│–+–––+ │ +––+ │ preallocated +–V–+–––+–––+–––+–––+–––+ read-only │ l │ a │ o │ t │ i │ e │ memory +–––+–––+–––+–––+–––+–––+ |
用白话解释就是,要是它不属于任何人,那我就直接把它放在内存里,然后引用它就完事了,我不关心你到底是谁的,我只知道我能读取你的内容就行了。
读完以上内容,我还希望你注意到一点,
用哪个?
显然,这取决于许多因素,但是总的来说,可以肯定的是,如果我们所写的API不依赖于拥有或者改变这个在使用的字符串,它应该是
1 2 3 | fn greet(name: &str) { println!("Hello, {}!", name); } |
但是,等一下!如果这个API的调用者真的只有
对Rust来说,完全不是问题,因为有一个超级强大的特性:强制解引用(deref coercing),允许你使用引用运算符&来转换任何传递的
1 2 3 4 5 6 7 8 9 10 11 | fn main() { let name1 = "lao wang"; let name2 = "zhang san".to_string(); greet(name1); greet(&name2); // `name2`被通过引用传递 } fn greet(name: &str) { println!("Hello, {}!", name); } |
代码
翻译完啦,其实这篇博客省去很多细节没讲,不过,该讲的重点,它们之间的区别,倒是讲清楚了,更多细节我会亲自写一篇博文专门介绍Rust中的字符串,有缘会再见,祝好!