前言
在rust
字符串中遇到的第一个障碍源自结果不合预期,一个字符串字面量"Hi!"
不是String
的实例,下面这段代码揭示了这样的结果:
fn main() {
print_type_of(&"Hi!");
print_type_of(&String::new());
}
fn print_type_of<T>(_: &T) {
println!("Type is: {}", std::any::type_name::<T>())
}
$ cargo run
Type is: &str
Type is: alloc::string::String
有趣的是,在JavaScript
中字符串字面量也不是String
的实例:
"Hi!" === "Hi!";
// > true
"Hi!" === new String("Hi!");
// > false
更多的例子:
typeof "Hi!";
// > "string"
typeof new String("Hi!");
// > "object"
typeof String("Hi!");
// > "string"
"Hi!" === String("Hi!");
// > true
String("Hi!") === new String("Hi!");
// > false
JavaScript
基本上抹平了string
字面量和String
对象的差异,如果你想在string
字面量调用String
对象的方法,JavaScript
会自动进行转化,不必你显示的做类型转化,rust
中也有类似的机制
相关阅读
关于String
有不少资料,官方文档的介绍不可错过,一定要仔细阅读
- Strings in the Rust docs
- Why Are There Two Types of Strings In Rust?
- How do I convert a &str to a String in Rust?
- Rust String vs str slices
- Rust: str vs String
- String vs &str in Rust
正文
&str
字符串字面量本质是一个字符串切片的引用,这意味着实际上它们是字符串数据的子字符串指针。rust
编译器会将我们所有的字符串字面量存放在某个地方,然后将它们的值替换为指针,这就让rust
优化掉了重复字符串的问题。你可以通过代码来验证这种优化现象,将下面代码中的字符串复制n
次,然后查看下编译打包出来的可执行文件体积:
fn main() {
print("TESTING:12345678901234567890123456789012345678901234567890");
}
fn print(msg: &str) {
println!("{}", msg);
}
我们可以对生成的二进制文件用strings
命令(这不是rust
提供的功能)进行探查:
$ strings target/release/200-prints | grep TESTING
TESTING:12345678901234567890123456789012345678901234567890
String
String
可以被改变,支持截取、缩小、增长等操作,当然这也会带来额外的成本
&str转化成String
简单来讲,用.to_owned()
方法可以将&str
(一个字符串切片的引用)变为有所有权的String
,例如:
let my_real_string = "string literal!".to_owned();
底层实际上是这样的:
String::from_utf8_unchecked(self.as_bytes().to_owned())
注意:
self
就是rust
中的this
这就是为什么要在学习string
前先要了解ownership
,字符串字面量只是一个引用,没有所有权,想变成有所有权的string
,需要进行格式的转化。是不是这意味着我们要每次都使用.to_owned()
呢?既是也不是,在介绍Traits
和generics
(泛型)之前你可以理解成“是的”
.to_string()、.into()、String::from()、format!()
标题列出的这些都可以将&str
变成String
,下面的我们将会给那些熟悉上述概念的人解释下为什么这些方法不正确
注意:
rust
中的``trait我们目前为止还未深入涉及到,你可以把它简单的当做是
JavaScript中的
mixin`(mixin pattern in JavaScript.)
为什么不用.to_string()
fn main() {
let real_string: String = "string literal".to_string();
needs_a_string("string literal".to_string());
}
fn needs_a_string(argument: String) {}
something.to_string()
会将something
转化为一个string
,经常用来实现Display trait
的一部分。你会看到很多文章推荐使用.to_string()
、另有很多文章不推荐。推荐中的细微差异在于你对编译器帮助所希望的程度,当你的程序变得庞大,特别是开始使用generics
(泛型)时,你会不可避免的需要做类型转换。一个最初是&str
的变量可能会转换成其它类型,如果新值仍然实现Display
那么就会有一个.to_string()
方法,编译器也不会有什么异议。另一方面,.to_owned()
会将引用的变量变成拥有的,通常是通过克隆实现。将一个引用的非string
的变量转化为拥有所有权的string
,编译器会报错。如果你觉的述差异可以接受,那么用.to_owned()
还是.to_string()
都无妨
注意:如果你对
&str
类型的变量使用.to_string()
,那么Clippy
会给出警告
为什么不用something.into()
fn main() {
let real_string: String = "string literal".into();
needs_a_string("string literal".into());
}
fn needs_a_string(argument: String) {}
something.into()
通过[dest_type]::from()
来努力将某个变量转变成目标类型,例如String::from(something)
。如果你想将&str
变成String
,你会得到想要的结果,不过稍后还有更好的方法
为什么不用String::from()
fn main() {
let real_string: String = String::from("string literal");
needs_a_string(String::from("string literal"));
}
fn needs_a_string(argument: String) {}
String::from(something)
比.into()
更加清晰,因为你可以明确的声明希望得到的类型,但是它和.to_string()
的问题一样
为什么不用format!()
fn main() {
let real_string: String = format!("string literal");
needs_a_string(format!("string literal"));
}
fn needs_a_string(argument: String) {}
format!()
是用来做格式化的,当只是简单的创建一个字符串时这是你最不该使用的方法
注意:
Clippy
有一个专门的规则 clippy::useless_format
实现细节
.to_owned()
:实现,调用String::from_utf8_unchecked(self.as_bytes().to_owned())
String::from()
:实现,调用.to_owned()
.to_string()
:实现,调用String::from()
.into()
:实现,调用String::from()
.format!()
:实现,调用Display::fmt
说明
总结
将&str
转换为String
是字符串问题的前半部分。后半部分是使用函数参数时,如果你想创建一个好用的API
,那么&str
和String
你选哪个?