rust中有两种字符串,一种是&str一种是String。
let s:&str = "hello world";
let s:String = s.to_string();
let s:&str = "hello world";
let s:String = s.to_string();
下面说说我对于这两种字符串的理解。
&str可以叫做string slice或者字符串字面量,在说字符串字面量之前,得先了解一下什么是切片(slice):
切片(Slice)是对数据值的部分引用。 切片这个名字往往出现在生物课上,我们做样本玻片的时候要从生物体上获取切片,以供在显微镜上观察。在 Rust 中,切片的意思大致也是这样,只不过它从数据取材引用。
我在学习golang时才了解slice的概念,和golang不同,rust中的slice是不能改变的。可以这么理解,&str就是str的不可变借用,不可变借用当然是不能改变的了。Rust字符串内部存储的是一个u8数组,但是这个数组是Unicode字符经过UTF-8编码得来的。而数组本身是存储在stack上的不可变的数据类型,所以&str实际上切的是数组的一部分。并且&str是没有容量的(capacity)的,只有String才有,让我们用如下String的内存图分析:
let my_name = "Pascal".to_string();
my_name的内存图:
buffer
/ capacity
/ / length
/ / /
+–––+–––+–––+
stack frame │ • │ 8 │ 6 │ <- my_name: String
+–│–+–––+–––+
│
[–│–––––––– capacity –––––––––––]
│
+–V–+–––+–––+–––+–––+–––+–––+–––+
heap │ P │ a │ s │ c │ a │ l │ │ │
+–––+–––+–––+–––+–––+–––+–––+–––+
[––––––– length ––––––––]
my_name.push_str( " Precht");
let last_name = &my_name[7..];
my_name: String last_name: &str
[––––––––––––] [–––––––]
+–––+––––+––––+–––+–––+–––+
stack frame │ • │ 16 │ 13 │ │ • │ 6 │
+–│–+––––+––––+–––+–│–+–––+
│ │
│ +–––––––––+
│ │
│ │
│ [–│––––––– str –––––––––]
+–V–+–––+–––+–––+–––+–––+–––+–V–+–––+–––+–––+–––+–––+–––+–––+–––+
heap │ P │ a │ s │ c │ a │ l │ │ P │ r │ e │ c │ h │ t │ │ │ │
+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+
let my_name = "Pascal".to_string();
my_name的内存图:
buffer
/ capacity
/ / length
/ / /
+–––+–––+–––+
stack frame │ • │ 8 │ 6 │ <- my_name: String
+–│–+–––+–––+
│
[–│–––––––– capacity –––––––––––]
│
+–V–+–––+–––+–––+–––+–––+–––+–––+
heap │ P │ a │ s │ c │ a │ l │ │ │
+–––+–––+–––+–––+–––+–––+–––+–––+
[––––––– length ––––––––]
my_name.push_str( " Precht");
let last_name = &my_name[7..];
my_name: String last_name: &str
[––––––––––––] [–––––––]
+–––+––––+––––+–––+–––+–––+
stack frame │ • │ 16 │ 13 │ │ • │ 6 │
+–│–+––––+––––+–––+–│–+–––+
│ │
│ +–––––––––+
│ │
│ │
│ [–│––––––– str –––––––––]
+–V–+–––+–––+–––+–––+–––+–––+–V–+–––+–––+–––+–––+–––+–––+–––+–––+
heap │ P │ a │ s │ c │ a │ l │ │ P │ r │ e │ c │ h │ t │ │ │ │
+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+
可以看到my_name实际的数据是在heap上存储的,在stack中有存储了三个信息,一个指向heap的指针,一个capacity,还有一个length,在往下看,可以看到last_name在栈上面没有存储容量的信息。
再看看使用字符串字面量创建的字符串内存图:
let my_name = "Pascal Precht";
my_name: &str
[–––––––––––]
+–––+–––+
stack frame │ • │ 6 │
+–│–+–––+
│
+––+
│
preallocated +–V–+–––+–––+–––+–––+–––+
read-only │ P │ a │ s │ c │ a │ l │
memory +–––+–––+–––+–––+–––+–––+
let my_name = "Pascal Precht";
my_name: &str
[–––––––––––]
+–––+–––+
stack frame │ • │ 6 │
+–│–+–––+
│
+––+
│
preallocated +–V–+–––+–––+–––+–––+–––+
read-only │ P │ a │ s │ c │ a │ l │
memory +–––+–––+–––+–––+–––+–––+
现在有个问题是如果一个slice是引用了其他的拥有者的,那么,如上所示,使用字面量建立的字符串,它的所有者是谁(谁拥有所有权?)。
其实字符串字面量是比较特别的,它引用的一部分存储在只读的预分配内存中,也就是说不依赖堆中分配的缓冲区。可以看到在执行程序时,stack上仍然有一个指向内存的指针。
最后
之前学习java,golang以及dart的时候,有些知识点当时是模糊不清的,但是又没有记录下来,导致回头去找问题的时候浪费了很多时间,所以这次学习rust,记录一下在学习过程中的一些疑问以及自己对于这些疑问的思考🤫,方便日后查看。