我不明白为什么必须克隆变量而不是引用变量

Raf*_*ael 1 rust

我正在阅读《Rust 编程语言》。在这段代码中,我不明白为什么它必须是args[1].clone()以及为什么不能是&args[1]

use std::env;
use std::fs;

fn main() {
    let args: Vec<String> = env::args().collect();

    let config = parse_config(&args);

    println!("Searching for {}", config.query);
    println!("In file {}", config.filename);

    let contents = fs::read_to_string(config.filename)
        .expect("Something went wrong reading the file");

    println!("With text:\n{}", contents);
}

struct Config {
    query: String,
    filename: String,
}

fn parse_config(args: &[String]) -> Config {
    let query = args[1].clone();
    let filename = args[2].clone();

    Config { query, filename }
}
Run Code Online (Sandbox Code Playgroud)

书上有解释,但我还是不明白。它说了一些关于结构取得所有权的事情。

这和上面的代码一样吗?args[1].clone这就是我更改为时编译器所说的操作&args[1]

fn parse_config(args: &[String]) -> Config {
    let query = &args[1];
    let filename = &args[2];

    Config { query: query.to_string(), filename: filename.to_string() }
}
Run Code Online (Sandbox Code Playgroud)

Edu*_*edo 9

既然你提到“结构取得所有权或其他东西”,我将稍微偏离你的问题来澄清一下事情。

\n

所有权的简化概述

\n

Rust 在不诉诸垃圾回收的情况下处理内存安全的方式是使用所有权借用的概念。这些概念需要时间来消化,但其中的某些方面可以通过简单的示例看出:

\n
struct Bike {\n   brand: String;\n   tire: f32;\n}\n\nimpl Bike {\n   fn ride_bike(&self) {\n       println!("Goin\' where the weather suits my clothes!")\n   }\n}\n\nfn main() {\n   // You are the owner of the bike\n   let rafaels_bike = Bike { \n      brand: String::from("Bike Friday"), \n      tire: 28.0 \n   }\n   \n   // You are giving your bike to me\n   let eduardos_bike = rafaels_bike\n\n   // Since you gave the bike to me, you cannot ride it anymore\n   // rafaels_bike.ride_bike()    // It will give a borrow after move error, if uncommented\n\n   // The bike is mine now, so I can ride it.\n   eduardos_bike.ride_bike()\n}\n
Run Code Online (Sandbox Code Playgroud)\n

Rust 这样做是因为它需要知道何时清理内存。为此,它会查看所有者的范围:当所有者超出范围时,就需要清理其资源。在这种情况下,Bike结构体分配内存,因此需要清理它。

\n

这种所有权概念渗透到 Rust 中的每个操作中,包括结构体和函数声明、表达式等。因此,声明函数的方式可以向调用者提供有关您想要对数据执行的操作的信息。

\n
// Immutable borrow: You\'re giving me permission to use your data (Bike), \n// but I can\'t change it\nfn have_a_look(bike: &Bike) \n\n// Mutable borrow: You\'re giving me permission to modify your data (Bike)\nfn install_modifications(bike: &mut Bike) \n\n// Moving operation: You\'re giving the bike away, you cannot use it anymore\n// after you call the function\nfn give_to_charity(bike: Bike)     // Bike is still available inside the function\n
Run Code Online (Sandbox Code Playgroud)\n

这也适用于结构声明,如果您声明一个属性而&后面没有,则表示它想要拥有数据**,因此结构Bike表示它想要拥有品牌String数据。好吧,自行车的类比在这里有点不成立,所以让我们回到你的问题。

\n

** 这里有一个小的不一致,适用于实现Copy 特征的值。如果一个值实现了 Copy 特征,rust 会在任何可以复制的地方复制它,所以你不需要费心在&类型之前添加一个,因为它发生在f32许多其他基本类型上。

\n

眼前的问题

\n

要理解为什么你不能做你想做的事(使用&args[1]而不是args[1].clone()),我们需要记住rust 中的所有权规则

\n
    \n
  1. Rust 中的每个值都有一个变量,\xe2\x80\x99s 称为其所有者。
  2. \n
  3. 一次只能有一位所有者。
  4. \n
  5. 当所有者超出范围时,该值将被删除。
  6. \n
\n

考虑到这一点,让我们看看该parse_config函数是否按照您想要的方式实现:

\n
// The Config struct wants to own both the query and filename strings\nstruct Config {\n    query: String,\n    filename: String,\n}\n\n// You\'re saying that you want to look at the args data and nothing else\nfn parse_config(args: &[String]) -> Config {\n    let query = &args[1];\n    let filename = &args[2];\n\n    // Then you\'re trying to give it away to Config behind the caller\'s back\n    // You broke the contract\n    Config { query, filename }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

本质上,如果允许的话,Stringsinargs将有两个所有者,argsconfig变量违反所有权规则 n\xc2\xb0 2 。

\n

假设 rust 没有那么严格,并且规则 n\xc2\xb0 2 不存在。在这个假设中,这是可能的:

\n
// This is a world without rule n\xc2\xb0 2, this code will not compile.\nuse std::env;\nuse std::fs;\n\nfn main() {\n    // args own the String inside the Vec\n    let args: Vec<String> = env::args().collect();\n\n    let contents = {\n        // config will also own the Strings at index 1 and 2\n        let config = parse_config(&args);\n\n        println!("Searching for {}", config.query);\n        println!("In file {}", config.filename);\n\n        fs::read_to_string(config.filename)\n            .expect("Something went wrong reading the file")\n\n        // config goes out of scope, cleaning the memory for strings it owns\n    };\n\n    // The strings at index 1 and 2 don\'t exist anymore\n    // This is known as a dangling pointer\n    println!("{:?}", args);\n\n    println!("With text:\\n{}", contents);\n\n    // End of scope: Will try to clean args memory, but some of it was already cleaned\n    // This is known as a double-free\n}\n\nstruct Config {\n    query: String,\n    filename: String,\n}\n\nfn parse_config(args: &[String]) -> Config {\n    let query = &args[1];\n    let filename = &args[2];\n    \n    // Give the strings to Config anyway\n    Config { query, filename }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

正如你所看到的,一切都会崩溃,Rust 将无法再保证内存安全。

\n

解决方案

\n

克隆

\n

本书提供的解决方案是克隆args字符串,这意味着您分配一个与所拥有的内存不同的全新内存块:

\n
    let query = args[1].clone();\n    let filename = args[2].clone();\n
Run Code Online (Sandbox Code Playgroud)\n

现在 和configargs可以了,因为它们拥有完全不同的内存块。当他们超出范围时,每个人都会清理他们所拥有的东西。

\n

进入函数

\n

这个已经提到过了。您可以将argsmain 中的变量移动到函数参数中。由于它现在拥有数据,因此您可以用它做任何您想做的事情,包括将所有权授予另一个结构。

\n
fn parse_config(mut args: Vec<String>) -> Config\n
Run Code Online (Sandbox Code Playgroud)\n

这是可行的,因为该函数在调用后main不使用该args变量。parse_config但您仍然无法使用索引,因为您无法移出索引,因为它是通过不可变借用发生的:

\n

来自索引特征文档

\n
fn index(&self, index: Idx) -> &Self::Output\n
Run Code Online (Sandbox Code Playgroud)\n

使用 to_string()

\n

这是您自己到达的,是的,它在功能上与书中提供的相同。

\n

to_stringfor的实现String如下:

\n
// to_string is implemented by means of to_owned\n#[stable(feature = "string_to_string_specialization", since = "1.17.0")]\nimpl ToString for String {\n    #[inline]\n    fn to_string(&self) -> String {\n        self.to_owned()\n    }\n}\n\n// to_owned uses clone when the type implements the Clone trait\n[stable(feature = "rust1", since = "1.0.0")]\nimpl<T> ToOwned for T\nwhere\n    T: Clone,\n{\n    type Owned = T;\n    fn to_owned(&self) -> T {\n        self.clone()\n    }\n\n    fn clone_into(&self, target: &mut T) {\n        target.clone_from(self);\n    }\n}\n\n// And String implements Clone\n#[stable(feature = "rust1", since = "1.0.0")]\nimpl Clone for String {\n    fn clone(&self) -> Self {\n        String { vec: self.vec.clone() }\n    }\n\n    fn clone_from(&mut self, source: &Self) {\n        self.vec.clone_from(&source.vec);\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

使用生命周期

\n

您还可以通过在Config结构中使用生命周期来解决此问题,如下所示:

\n
struct Config<\'a> {\n    query: &\'a String,\n    filename: &\'a String,\n}\n\nfn parse_config(args: &[String]) -> Config {\n    let query = &args[1];\n    let filename = &args[2];\n\n    // Now config is not trying to own the Strings\n    // So you can pass your references to it\n    Config { query, filename }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

这允许您按照您想要的方式实现代码,但它将结果的生命周期Config与 的生命周期联系起来args,在本例中是main函数作用域。

\n

换句话说,您只能在初始所有者 ( in ) 仍在范围内时访问config.filename和。config.queryargsmain

\n

我已经创建了一个包含工作示例的游乐场和另一个具有不同参数范围的游乐场,以便您可以看到生命周期如何影响返回。

\n
\n

我不是 Rust 专业人士,为了让解释更简单,我做了一些简化。如果我说错了什么,希望有经验的 rustacean 能够纠正我。

\n