了解共享引用对嵌套数据结构的影响

use*_*869 5 rust

所有权树

\n

所有权树

\n

你好,

\n

我试图理解 Rust 中的所有权概念,并在“Programming Rust”一书中看到了这张图片(附在这篇文章中)。

\n

我特别关心“借用共享参考”部分。作者在书中说道

\n
\n

共享引用借用的值是只读的。在共享引用的整个生命周期中,其引用对象以及从该引用对象可访问的任何内容都不能被任何内容更改。不存在对该结构中任何内容的实时可变引用,其所有者是只读的,等等。它\xe2\x80\x99s真的冻结了

\n
\n

在图中,他继续强调沿着所有权树的路径,一旦对所有权树的特定部分采用共享引用,该路径就变得不可变。但令我困惑的是,作者还提到所有权树的某些其他部分不是只读的。

\n

所以我尝试用这段代码进行测试:

\n
 fn main(){                                                                                                    \n    let mut v = Vec::new();                                                                                   \n    v.push(Vec::new());                                                                                       \n    v[0].push(vec!["alpha".to_string()]);                                                                                    \n    v[0].push(vec!["beta".to_string(), "gamma".to_string()]);                                                                                                                                                                                                                                                \n    let r2 = &(v[0][1]); //Taking a shared reference here                                                                                    \n    v[0][0].push("pi".to_string());                                                                           \n    println!("{:?}", r2)                                                                                      \n}\n
Run Code Online (Sandbox Code Playgroud)\n

我知道它v[0][0]不能是可变的,因为v它本身是一个不可变的共享引用(作为对 的共享引用的结果v[0][1])并且 Rust 编译器有用地指出了这一点。我的问题是,当作者将所有权树上的某些部分标记为“非只读”时,我们如何访问这些部分来更改它们?

\n

如果我的代码片段不是作者想要传达的内容的正确示例,请帮助我提供一个示例来演示作者在这里试图暗示的内容。谢谢。

\n

Kev*_*eid 5

在某些特殊情况下,您可以拆分借用,同时创建现有引用,这些引用可以是可变和不可变的任意组合,只要它们不重叠即可。这些都是:

\n
    \n
  • 编译器可以静态跟踪缺少重叠的任何内容:即结构、元组或枚举中的字段。
  • \n
  • 提供此功能的专门编写的unsafe代码,例如集合上的可变引用迭代器。
  • \n
\n

您编写的代码无法编译,因为编译器不会尝试理解索引 a 的作用,因此它不具备也无法使用不重叠的Vec事实。v[0][0]v[0][1]

\n

这是直接翻译图中所示树的程序:

\n
#[derive(Debug)]\nstruct Things {\n    label: &\'static str,\n    a: Option<Box<Things>>,\n    b: Option<Box<Things>>,\n    c: Option<Box<Things>>,\n}\n\nfn main() {\n    // Construct depicted structure\n    let mut root = Box::new(Things {\n        label: "root",\n        a: None,\n        b: None,\n        c: Some(Box::new(Things {\n            label: "root.c",\n            a: None,\n            b: None,\n            c: None,\n        })),\n    });\n\n    // "Borrowing a shared reference"\n    // .as_ref().unwrap() gets `&Things` out of `&Option<Things>`\n    // (there are several other ways this could be done)\n    let shared_reference = &root.c.as_ref().unwrap();\n    let mutable_reference = &mut root.a;\n    // Now, root and root.a are in the "inaccessible" state because they are\n    // borrowed. (We could still create an &root.b reference).\n\n    // Mutate while the shared reference must still exist\n    dbg!(shared_reference);\n    *mutable_reference = Some(Box::new(Things {\n        label: "new",\n        a: None,\n        b: None,\n        c: None,\n    }));\n    dbg!(shared_reference);\n\n    // Now the references are not used any more, so we can access the root.\n    // Let\'s look at the change we made.\n    dbg!(root);\n}\n
Run Code Online (Sandbox Code Playgroud)\n

该程序被编译器接受,因为它理解结构体字段不重叠,因此根可能会被分割。

\n

可以不使用索引运算符来拆分向量 \xe2\x80\x94\xc2\xa0 的借用您可以通过模式匹配、可变迭代或使用.split_at_mut(). 这是最后一个选项,它是最有能力进行随机访问的选项:

\n
fn main() {                                                                                                    \n    let mut v = Vec::new();\n    v.push(Vec::new());\n    v[0].push(vec!["alpha".to_string()]);\n    v[0].push(vec!["beta".to_string(), "gamma".to_string()]);\n    \n    let (half1, half2): (&mut [Vec<String>], &mut [Vec<String>]) =\n        v[0].split_at_mut(1);\n    let r1 = &mut half1[0];\n    let r2 = &half2[0];\n\n    r1.push("pi".to_string());\n    println!("{:?}", r2);\n}\n
Run Code Online (Sandbox Code Playgroud)\n

该程序之所以有效,是因为split_at_mut()包含unsafe专门创建两个不重叠切片的代码。这是 Rust 的基本工具之一:使用unsafe库内部来创建健全的抽象,而仅使用编译器理解的概念是不可能实现的。

\n

如果使用模式匹配,则为:

\n
    if let [r1, r2] = &mut *v[0] {\n        r1.push("pi".to_string());\n        println!("{:?}", r2);\n    } else {\n        // Pattern failed because the length did not match\n        panic!("oops, v was not two elements long");\n    }\n
Run Code Online (Sandbox Code Playgroud)\n

之所以能够编译,是因为编译器知道模式匹配切片(或结构体,或任何其他可匹配的内容)会创建对每个元素的非重叠引用。(模式匹配由编译器实现,从不运行 Rust 代码来做出有关匹配结构的决定。)

\n

(这个版本有一个明确的失败分支;以前的版本会在split_at_mut()或 上half2[0]ifv[0]太短而出现恐慌。)

\n