Rust引用
- 接续上一章,Rust中使用元组进行返回string时,是使用了string本身,但是使用对象的引用作为参数能够更好执行。
fn main() {
let s1 = String::from("Hello!");
let len = calculate_length(&s1);
println!("The length of {} is {}",s1,len);
}
fn calculate_length(s: &String) -> usize
{
s.len()
}
这个代码中就使用了引用,
下面是官方文档
- 这里的s中指针指向s1指针,通过s1指针再指向数据索引,完成引用,但是并不会拥有这个值,在引用停止时值也不会被抛弃
- 相对应的有解引用,之后会介绍
- 引用标识符是&
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("The length of '{}' is {}.", s1, len);
}
fn calculate_length(s: &String) -> usize { // s 是对 String 的引用
s.len()
} // 这里,s 离开了作用域。但因为它并不拥有引用值的所有权,
// 所以什么也不会发生
- 因为没有所有权,所以在引用结束之后并不丢弃它指向的数据。
将创建一个引用的行称为借用,引用默认状态下不允许修改被引用的变量值
fn main() {
let s = String::from("hello");
change(&s);
}
fn change(some_string: &String) {
some_string.push_str(", world");
}
$ cargo run
Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0596]: cannot borrow `*some_string` as mutable, as it is behind a `&` reference
--> src/main.rs:8:5
|
7 | fn change(some_string: &String) {
| ------- help: consider changing this to be a mutable reference: `&mut String`
8 | some_string.push_str(", world");
| ^^^^^^^^^^^ `some_string` is a `&` reference, so the data it refers to cannot be borrowed as mutable
For more information about this error, try `rustc --explain E0596`.
error: could not compile `ownership` due to previous error
可变引用
- 在上段代码基础上添加mut将其变为可变引用。
- 可变引用在同一时间下,只能有一个对应一个特殊的可变引用,尝试使用两个将会失败。
fn main() {
let mut s = String::from("hello");
change(&mut s);
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
fn main() {
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s;
println!("{}, {}", r1, r2);
}
$ cargo run
Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0499]: cannot borrow `s` as mutable more than once at a time
--> src/main.rs:5:14
|
4 | let r1 = &mut s;
| ------ first mutable borrow occurs here
5 | let r2 = &mut s;
| ^^^^^^ second mutable borrow occurs here
6 |
7 | println!("{}, {}", r1, r2);
| -- first borrow later used here
For more information about this error, try `rustc --explain E0499`.
error: could not compile `ownership` due to previous error
- 这样限制的好处是Rust在编译过程中避免数据竞争。
数据竞争产生条件
- 两个或更多指针同时访问同一数据。
- 至少有一个指针被用来写入数据。
- 没有同步数据访问的机制。
避免数据竞争最好的方式是使用不同作用域。
fn main() {
let mut s = String::from("hello");
{
let r1 = &mut s;
} // r1 在这里离开了作用域,所以我们完全可以创建一个新的引用
let r2 = &mut s;
}
- 同时也不能在使用不可变引用的同时使用可变引用,但是可以同时使用多个不可变引用,因为只能读取的变量不会相互影响。
fn main() {
let mut s = String::from("hello");
let r1 = &s; // 没问题
let r2 = &s; // 没问题
let r3 = &mut s; // 大问题
println!("{}, {}, and {}", r1, r2, r3);
}
$ cargo run
Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
--> src/main.rs:6:14
|
4 | let r1 = &s; // no problem
| -- immutable borrow occurs here
5 | let r2 = &s; // no problem
6 | let r3 = &mut s; // BIG PROBLEM
| ^^^^^^ mutable borrow occurs here
7 |
8 | println!("{}, {}, and {}", r1, r2, r3);
| -- immutable borrow later used here
For more information about this error, try `rustc --explain E0502`.
error: could not compile `ownership` due to previous error
- 可以在不可变引用作用域结束的时候使用可变引用。
fn main() {
let mut s = String::from("hello");
let r1 = &s; // 没问题
let r2 = &s; // 没问题
println!("{} and {}", r1, r2);
// 此位置之后 r1 和 r2 不再使用
let r3 = &mut s; // 没问题
println!("{}", r3);
}
悬垂引用
- 其他具有指针的语言中很容易通过释放内存时保留指向它的指针错误生成悬垂指针,这个指针中指向的内存已经不是之前的内存。
- Rust中则保证了数据不会在引用之前离开作用域。
fn main() {
let reference_to_nothing = dangle();
}
fn dangle() -> &String {
let s = String::from("hello");
&s
}
$ cargo run
Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0106]: missing lifetime specifier
--> src/main.rs:5:16
|
5 | fn dangle() -> &String {
| ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
help: consider using the `'static` lifetime
|
5 | fn dangle() -> &'static String {
| ^^^^^^^^
For more information about this error, try `rustc --explain E0106`.
error: could not compile `ownership` due to previous error
- 在这段代码中由于没有生命周期,所以没法引用,因为这个引用会造成悬垂指针。
- 但是如果在函数中使用返回String:
fn main() {
let string = no_dangle();
}
fn no_dangle() -> String {
let s = String::from("hello");
s
}
- 将s所有权移出,值不被释放,此时不会有错误。
切片slice类型
- slice数据类型也没有所有权,slice允许使用集合中一段连续元素序列,不需用引用所有集合。
- 下面是一段官方文档:
- 需要逐个检查
String
中值是否为空格,使用as_bytes
将String
转换为字节数组:
let bytes = s.as_bytes();
- 使用
iter
在字节数组上创建迭代器,iter()返回集合中每个元素,enumerate
包装iter
结果, - enumerate中第一个元素是索引,第二个元素是集合中元素引用。返回的结果是
(i,&item)
。 - 通过字节的字面量语法寻找代表空格的字节,找到空格返回空格位置,负责返回s.len()。
- 返回了独立的
usize
数字,但是无法保证是否有效。
fn first_word(s: &String) -> usize {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
//找到空格,b' '表示空格字节
if item == b' '
{
return i;
}
}
s.len()
}
fn main() {
let mut s = String::from("hello world");
let word = first_word(&s); // word 的值为 5
s.clear(); // 这清空了字符串,使其等于 ""
// word 在此处的值仍然是 5,
// 但是没有更多的字符串让我们可以有效地应用数值 5。word 的值现在完全无效!
}
- word此时值仍然为5,但是很难将word中索引和s相对应。在将5保存到word中时s中的内容改变(这里可能是地址)
字符串slice
- 字符串slice类似这样,对String中一部分值的引用。
fn main() {
let s = String::from("hello world");
let hello = &s[0..5];
let world = &s[6..11];
}
接下来是一段官方文档的描述:
- Rust中range语法引用和slice相同。
fn main() {
let s = String::from("hello");
let slice = &s[0..2];
let slice = &s[..2];
}
- 以下语法也是相同的
fn main() {
let s = String::from("hello");
let len = s.len();
let slice = &s[3..len];
let slice = &s[3..];
}
也可以舍弃[start..end]
中的start
和end
fn main() {
let s = String::from("hello");
let len = s.len();
let slice = &s[0..len];
let slice = &s[..];
}
- 字符串slice的声明写做
&str
fn first_word(s: &String) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
fn main() {
let mut s = String::from("hello world");
let word = first_word(&s);
s.clear(); // error!
println!("the first word is: {}", word);}
这时候如果直接编译会出现错误。因为s.clear()会清除String,Rust不允许word中不可变引用和clear中可变引用同时存在,导致编译失败。
修改过后的代码
- 取消清除
s.clear()
字符串字面量就是slice
let s = "hello world!";
- 这里的s类型是
&str
,是一个指向二进制程序特定位置的slice,字符串字面变量是不可变的,&str
是一个不可变引用
fn first_word(s: &String) -> &str
//下面代码可以对String和&str使用相同函数。
fn first_word(s: &str) -> &str
- 修改过后,会使得我们的API更加通用,不会丢失内容。
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
fn main() {
let my_string = String::from("hello world");
// `first_word` 接受 `String` 的切片,无论是部分还是全部
let word = first_word(&my_string[0..6]);
let word = first_word(&my_string[..]);
// `first_word` 也接受 `String` 的引用,
// 这等同于 `String` 的全部切片
let word = first_word(&my_string);
let my_string_literal = "hello world";
// `first_word` 接受字符串字面量的切片,无论是部分还是全部
let word = first_word(&my_string_literal[0..6]);
let word = first_word(&my_string_literal[..]);
// 因为字符串字面值**就是**字符串 slice,
// 这样写也可以,即不使用 slice 语法!
let word = first_word(my_string_literal);
}
其它类型的slice
let a = [1,2,3,4,5];
let a = [1,2,3,4,5];
let slice = &a[1..3];
- 这种slice类型是
&[i32]
,和字符串slice工作方式相同。