引用

引用

堆栈,堆和指针在 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: 我们给予 countryprint_country. print_country 没有一个 ->, 因此它不会返回任何内容。后 print_country 完成,我们的 String 现在已经死了。
  • Step 3: 我们尝试给 countryprint_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);
}
上一页