引用
引用
堆栈,堆和指针在 Rust 中非常重要;堆栈和堆是保留内存的两个位置。重要的区别是:
- 堆栈非常快,但是堆却没有那么快。
- 堆栈需要在编译时知道变量的大小。因此,像 i32 这样的简单变量就会进入堆栈,因为我们知道它们的确切大小。
- 有些类型在编译时不知道大小。但是堆栈需要知道确切的大小。你是做什么?您将数据放入堆中,因为堆可以具有任何大小的数据。为了找到它,将指针放在堆栈上,因为我们一直都知道指针的大小。
指针就像书中的目录:
MY BOOK
Chapter Page
Chapter 1: My life 1
Chapter 2: My cat 15
Chapter 3: My job 23
Chapter 4: My family 30
Chapter 5: Future plans 43
您通常在 Rust 中看到的指针称为引用。这是要了解的重要部分:引用指向另一个值的内存。引用表示您借用了该值,但没有该值。在 Rust 中,引用中有&。所以:let my_variable = 8
做一个常规变量,但是 let my_reference = &my_variable
会创建一个引用。这意味着 my_reference
仅查看 my_variable
的数据,my_variable
仍然拥有其数据。
Reference
Reference 在 Rust 中非常重要。Rust 使用引用来确保所有内存访问都是安全的。我们知道我们使用 & 来创建 Reference:
fn main() {
let country = String::from("Austria");
let ref_one = &country;
let ref_two = &country;
println!("{}", ref_one);
}
country
是 String 类型,我们创建了指向它的两个引用,它们的类型就是 &String,我们还可以根据需要创建无数个这样的引用。
fn main() {
let country = return_str();
}
fn return_str() -> &str {
let country = String::from("Austria");
let country_ref = &country;
country_ref // ⚠️
}
函数 return_str()
创建一个字符串,然后创建对该字符串的引用。然后,它尝试返回引用。但是 country
只存在于函数内部。因此,函数结束后,country_ref
指的是已经消失的内存。Rust 可以防止我们在内存方面犯错误。
反引用与点运算符
我们了解到,当您有参考时,需要使用 *
来获取值。引用是另一种类型,因此无法使用:
fn main() {
let my_number = 9;
let reference = &my_number;
println!("{}", my_number == reference); // ⚠️
}
error[E0277]: can't compare `{integer}` with `&{integer}`
--> src\main.rs:5:30
|
5 | println!("{}", my_number == reference);
| ^^ no implementation for `{integer} == &{integer}`
我们将第 5 行更改为 println!("{}", my_number == *reference);
就可以了,这也就是所谓的 Dereferencing。但是,当您使用一种方法时,Rust 将为您取消引用,函数中的 .
称为点运算符。
struct Item {
number: u8,
}
fn main() {
let item = Item {
number: 8,
};
let reference_number = &item.number; // reference number type is &u8
println!("{}", reference_number == 8); // ⚠️ &u8 and u8 cannot be compared
}
点运算符
为了使其工作,我们需要取消引用:println!("{}", *reference_number == 8);
,但是使用点运算符,我们不需要 *
。例如:
struct Item {
number: u8,
}
fn main() {
let item = Item {
number: 8,
};
let reference_item = &item;
println!("{}", reference_item.number == 8); // we don't need to write *reference_item.number
}
现在让我们为 Item 创建一个将数字与另一个数字进行比较的方法。我们不需要在任何地方使用 *
:
struct Item {
number: u8,
}
impl Item {
fn compare_number(&self, other_number: u8) { // takes a reference to self
println!("Are {} and {} equal? {}", self.number, other_number, self.number == other_number);
// We don't need to write *self.number
}
}
fn main() {
let item = Item {
number: 8,
};
let reference_item = &item; // This is type &Item
let reference_item_two = &reference_item; // This is type &&Item
item.compare_number(8); // the method works
reference_item.compare_number(8); // it works here too
reference_item_two.compare_number(8); // and here
}
因此,请记住:使用时 .
运算符,您不必担心 *
。
Mutable references
如果要使用引用来更改数据,则可以使用可变引用。为了便于参考,您可以编写 &mut。
fn main() {
let mut my_number = 8; // don't forget to write mut here!
let num_ref = &mut my_number;
}
因此,让我们将其添加到 my_number
中使用 10。但是您不能编写 num_ref + = 10
,因为 num_ref
不是 i32 值。为了到达值所在的地方,我们使用 *
。*
表示我不需要引用,我想要引用后面的值。换句话说,一个 *
会删除一个&。
fn main() {
let mut my_number = 8;
let num_ref = &mut my_number;
*num_ref += 10; // Use * to change the i32 value.
println!("{}", my_number);
}
因为使用 &
被称作 “referencing”, 因此使用 *
被称作 “dereferencing”,Rust 具有可变和不可变引用的规则:
- Rule 1: 如果您只有不可变的引用,则可以有任意多个。
- Rule 2: 如果您有可变的引用,则只能有一个。您不能有不可变的引用和可变的引用。
这是因为可变引用可以更改数据。如果在其他引用正在读取时更改数据,则可能会遇到问题。这是带有不可变借位的可变借位的示例:
fn main() {
let mut number = 10;
let number_ref = &number;
let number_change = &mut number;
*number_change += 10;
println!("{}", number_ref); // ⚠️
}
error[E0502]: cannot borrow `number` as mutable because it is also borrowed as immutable
--> src\main.rs:4:25
|
3 | let number_ref = &number;
| ------- immutable borrow occurs here
4 | let number_change = &mut number;
| ^^^^^^^^^^^ mutable borrow occurs here
5 | *number_change += 10;
6 | println!("{}", number_ref);
| ---------- immutable borrow later used here
不过如下的用法是可以的:
fn main() {
let mut number = 10;
let number_change = &mut number; // create a mutable reference
*number_change += 10; // use mutable reference to add 10
let number_ref = &number; // create an immutable reference
println!("{}", number_ref); // print the immutable reference
}
编译器知道我们使用 number_change
来更改数字,但是没有再次使用它,即我们不会同时使用不可变和可变的引用。
函数引用
引用对于函数非常有用。Rust 关于变量的规则是:一个变量只能有一个所有者。
fn main() {
let country = String::from("Austria");
print_country(country); // We print "Austria"
print_country(country); // ⚠️ That was fun, let's do it again!
}
fn print_country(country_name: String) {
println!("{}", country_name);
}
它不起作用,因为 country
被销毁了。这是如何做:
- Step 1: 我们创建
String
country
.country
是所有者。 - Step 2: 我们给予
country
至print_country
.print_country
没有一个->
, 因此它不会返回任何内容。后print_country
完成,我们的String
现在已经死了。 - Step 3: 我们尝试给
country
至print_country
,但我们已经做到了。我们没有country
给予。
我们可以通过添加 &
来解决这个问题:
fn main() {
let country = String::from("Austria");
print_country(&country); // We print "Austria"
print_country(&country); // That was fun, let's do it again!
}
fn print_country(country_name: &String) {
println!("{}", country_name);
}
这是使用可变变量的函数示例。
fn main() {
let mut country = String::from("Austria");
add_hungary(&mut country);
}
fn add_hungary(country_name: &mut String) {
country_name.push_str("-Hungary"); // push_str() adds a &str to a String
println!("Now it says: {}", country_name);
}
因此得出结论:
fn function_name(variable: String)
接受一个字符串并拥有它。如果没有返回任何内容,则该变量将在函数完成后死亡。fn function_name(variable: &String)
借用一个字符串,可以看一下。fn function_name(variable: &mut String)
借用字符串并可以更改它。
这是一个看起来像可变引用的示例,但有所不同。
fn main() {
let country = String::from("Austria"); // country is not mutable
adds_hungary(country);
}
fn adds_hungary(mut country: String) { // but adds_hungary takes the string and it is mutable!
country.push_str("-Hungary");
println!("{}", country);
}
这怎么可能?这是因为 mut country
不是引用:adds_hungary 现在拥有 country 变量,它使用 String 而不是 &String。add_hungary 是完全所有者,因此可以将国家/地区视为可变的。
Clone
fn main() {
let country = String::from("Kiribati");
prints_country(country.clone()); // make a clone and give it to the function
prints_country(country);
}
fn prints_country(country_name: String) {
println!("{}", country_name);
}
当然,如果 String 很大,.clone() 会占用大量内存。例如,一个 String 可以是整本书,每当我们调用 .clone() 时,它将复制该书。因此,如果可以的话,使用 & 作为引用会更快。
fn main() {
let mut country = String::from("Kiribati"); // country is mutable
let country_ref = &country; // country_ref needs a reference
changes_country(&mut country); // changes_country needs a &mut ref
println!("{}", country_ref); // ⚠️ immutable and mutable borrow together
}
fn prints_country(country_name: String) {
println!("{}", country_name);
}
fn changes_country(country_name: &mut String) {
country_name.push_str(" is a country");
println!("{}", country_name);
}
也许我们可以更改顺序,但也可以只使用 .clone()。
fn main() {
let mut country = String::from("Kiribati"); // country is mutable
let country_ref = &country; // country_ref needs a reference
changes_country(&mut country.clone()); // give changes_country a clone instead
println!("{}", country_ref); // now the code works
}
fn prints_country(country_name: String) {
println!("{}", country_name);
}
fn changes_country(country_name: &mut String) {
country_name.push_str(" is a country");
println!("{}", country_name);
}