gpt4 book ai didi

rust - 为什么我不能在同一个结构中存储一个值和对该值的引用?

转载 作者:行者123 更新时间:2023-12-03 04:11:16 25 4
gpt4 key购买 nike

我有一个值,我想存储该值和对
在我自己的类型中该值中的某些内容:

struct Thing {
count: u32,
}

struct Combined<'a>(Thing, &'a u32);

fn make_combined<'a>() -> Combined<'a> {
let thing = Thing { count: 42 };

Combined(thing, &thing.count)
}

有时,我有一个值,我想存储该值和对
该值在同一结构中:
struct Combined<'a>(Thing, &'a Thing);

fn make_combined<'a>() -> Combined<'a> {
let thing = Thing::new();

Combined(thing, &thing)
}

有时,我什至没有引用值(value),我得到了
同样的错误:
struct Combined<'a>(Parent, Child<'a>);

fn make_combined<'a>() -> Combined<'a> {
let parent = Parent::new();
let child = parent.child();

Combined(parent, child)
}

在这些情况中的每一种情况下,我都会收到一个错误,其中一个值“做
活得不够久”。这个错误是什么意思?

最佳答案

我们来看a simple implementation of this :

struct Parent {
count: u32,
}

struct Child<'a> {
parent: &'a Parent,
}

struct Combined<'a> {
parent: Parent,
child: Child<'a>,
}

impl<'a> Combined<'a> {
fn new() -> Self {
let parent = Parent { count: 42 };
let child = Child { parent: &parent };

Combined { parent, child }
}
}

fn main() {}
这将失败并出现错误:
error[E0515]: cannot return value referencing local variable `parent`
--> src/main.rs:19:9
|
17 | let child = Child { parent: &parent };
| ------- `parent` is borrowed here
18 |
19 | Combined { parent, child }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ returns a value referencing data owned by the current function

error[E0505]: cannot move out of `parent` because it is borrowed
--> src/main.rs:19:20
|
14 | impl<'a> Combined<'a> {
| -- lifetime `'a` defined here
...
17 | let child = Child { parent: &parent };
| ------- borrow of `parent` occurs here
18 |
19 | Combined { parent, child }
| -----------^^^^^^---------
| | |
| | move out of `parent` occurs here
| returning this value requires that `parent` is borrowed for `'a`
要完全理解此错误,您必须考虑如何
值在内存中表示,移动时会发生什么
那些值(value)观。让我们注释 Combined::new与一些假设
显示值所在位置的内存地址:
let parent = Parent { count: 42 };
// `parent` lives at address 0x1000 and takes up 4 bytes
// The value of `parent` is 42
let child = Child { parent: &parent };
// `child` lives at address 0x1010 and takes up 4 bytes
// The value of `child` is 0x1000

Combined { parent, child }
// The return value lives at address 0x2000 and takes up 8 bytes
// `parent` is moved to 0x2000
// `child` is ... ?
child应该怎么办? ?如果值只是像 parent 那样移动
是,那么它将指不再保证的内存
有一个有效的值(value)。允许存储任何其他代码
内存地址 0x1000 处的值。假设它是访问该内存
整数可能会导致崩溃和/或安全错误,并且是其中之一
Rust 防止的主要错误类别。
这正是生命周期防止的问题。一生是一个
允许您和编译器知道多长时间的元数据
值将在其 处有效当前内存位置 .那是一个
重要的区别,因为这是 Rust 新手常犯的错误。
Rust 的生命周期不是一个对象出现的时间段
创建和销毁时!
打个比方,这样想:在一个人的一生中,他们会
居住在许多不同的位置,每个位置都有不同的地址。一种
Rust 生命周期与您当前居住的地址有关,
不是关于你将来什么时候死(虽然也会死
更改您的地址)。每次你移动它都是相关的,因为你的
地址不再有效。
同样重要的是要注意生命周期不会改变你的代码;您的
代码控制生命周期,你的生命周期不控制代码。这
精辟的说法是“生命是描述性的,而不是规定性的”。
让我们注释 Combined::new一些我们将使用的行号
突出生命周期:
{                                          // 0
let parent = Parent { count: 42 }; // 1
let child = Child { parent: &parent }; // 2
// 3
Combined { parent, child } // 4
} // 5
parent的混凝土生命周期是从 1 到 4,包括(我将
表示为 [1,4] )。 child的混凝土生命周期是 [2,4] , 和
返回值的具体生命周期为 [4,5] .它是
可能有从零开始的具体生命周期 - 那将
表示函数或某些东西的参数的生命周期
存在于区块之外。
注意 child的生命周期本身是 [2,4] ,但它 指的是
生命周期为 [1,4] 的值.这很好,只要
引用值在被引用值生效之前变为无效。这
当我们尝试返回 child 时出现问题来自街区。这个会
“过度延长”生命周期超出其自然长度。
这个新知识应该可以解释前两个例子。第三
一是需要查看 Parent::child的实现.机会
是,它看起来像这样:
impl Parent {
fn child(&self) -> Child { /* ... */ }
}
这使用生命周期省略来避免编写显式泛型
生命周期参数。它相当于:
impl Parent {
fn child<'a>(&'a self) -> Child<'a> { /* ... */ }
}
在这两种情况下,该方法都表示 Child结构将是
返回已用具体生命周期参数化的 self .换句话说, Child实例包含一个引用
Parent创造了它,因此不能活得更久 Parent实例。
这也让我们认识到我们的东西确实有问题
创建函数:
fn make_combined<'a>() -> Combined<'a> { /* ... */ }
尽管您更有可能看到以不同形式编写的内容:
impl<'a> Combined<'a> {
fn new() -> Combined<'a> { /* ... */ }
}
在这两种情况下,都没有通过
争论。这意味着 Combined 的生命周期将
参数化不受任何限制 - 它可以是任何东西
来电者希望它成为。这是荒谬的,因为调用者
可以指定 'static一生都没有办法遇见那个
状况。
我如何解决它?
最简单和最推荐的解决方案是不要尝试放置
这些项目在同一结构中。通过这样做,您的
结构嵌套将模拟代码的生命周期。地点类型
将自己的数据整合到一个结构中,然后提供方法
允许您根据需要获取引用或包含引用的对象。
有一种特殊情况,生命周期跟踪过于热情:
当你有东西放在堆上时。当您使用 Box<T> , 例如。在这种情况下,移动的结构
包含一个指向堆的指针。指向的值将保留
稳定,但指针本身的地址会移动。在实践中,
这无关紧要,因为您始终遵循指针。
一些 crate 提供了表示这种情况的方法,但它们
要求基地址永不移动。这排除了变异
向量,这可能会导致重新分配和移动
堆分配的值。
  • rental (不再维护或支持)
  • owning_ref
  • ouroboros

  • 通过租赁解决的问题示例:
  • Is there an owned version of String::chars?
  • Returning a RWLockReadGuard independently from a method
  • How can I return an iterator over a locked struct member in Rust?
  • How to return a reference to a sub-value of a value that is under a mutex?
  • How do I store a result using Serde Zero-copy deserialization of a Futures-enabled Hyper Chunk?
  • How to store a reference without having to deal with lifetimes?

  • 在其他情况下,您可能希望转向某种类型的引用计数,例如使用 Rc Arc .
    更多信息

    After moving parent into the struct, why is the compiler not able to get a new reference to parent and assign it to child in the struct?


    虽然理论上可以这样做,但这样做会引入大量的复杂性和开销。每次移动对象时,编译器都需要插入代码来“修复”引用。这意味着复制结构不再是一个非常廉价的操作,只需移动一些位。这甚至可能意味着这样的代码很昂贵,这取决于假设的优化器有多好:
    let a = Object::new();
    let b = a;
    let c = b;
    程序员不必在每次移动时都强制执行此操作,而是可以通过创建仅在您调用它们时采用适当引用的方法来选择何时发生此操作。
    引用自身的类型
    在一种特定情况下,您可以创建一个引用自身的类型。你需要使用类似 Option 的东西不过要分两步完成:
    #[derive(Debug)]
    struct WhatAboutThis<'a> {
    name: String,
    nickname: Option<&'a str>,
    }

    fn main() {
    let mut tricky = WhatAboutThis {
    name: "Annabelle".to_string(),
    nickname: None,
    };
    tricky.nickname = Some(&tricky.name[..4]);

    println!("{:?}", tricky);
    }
    从某种意义上说,这确实有效,但创造的值(value)受到高度限制——它永远无法移动。值得注意的是,这意味着它不能从函数返回或按值传递给任何东西。构造函数显示了与上述生命周期相同的问题:
    fn creator<'a>() -> WhatAboutThis<'a> { /* ... */ }
    如果您尝试使用方法执行相同的代码,您将需要诱人但最终无用的 &'a self .当涉及到时,此代码将受到更多限制,您将在第一个方法调用后收到借用检查器错误:
    #[derive(Debug)]
    struct WhatAboutThis<'a> {
    name: String,
    nickname: Option<&'a str>,
    }

    impl<'a> WhatAboutThis<'a> {
    fn tie_the_knot(&'a mut self) {
    self.nickname = Some(&self.name[..4]);
    }
    }

    fn main() {
    let mut tricky = WhatAboutThis {
    name: "Annabelle".to_string(),
    nickname: None,
    };
    tricky.tie_the_knot();

    // cannot borrow `tricky` as immutable because it is also borrowed as mutable
    // println!("{:?}", tricky);
    }
    也可以看看:
  • Cannot borrow as mutable more than once at a time in one code - but can in another very similar

  • 怎么样 Pin ?
    Pin ,在 Rust 1.33 中稳定,有这个 in the module documentation :

    A prime example of such a scenario would be building self-referential structs, since moving an object with pointers to itself will invalidate them, which could cause undefined behavior.


    需要注意的是,“自我引用”并不一定意味着使用引用。确实, example of a self-referential struct具体说(强调我的):

    We cannot inform the compiler about that with a normal reference,since this pattern cannot be described with the usual borrowing rules.Instead we use a raw pointer, though one which is known to not be null,since we know it's pointing at the string.


    自 Rust 1.0 以来,就已经存在使用原始指针来实现这种行为的能力。事实上,拥有引用和租赁在引擎盖下使用原始指针。
    唯一的事情 Pin添加到表中是声明给定值保证不会移动的常用方法。
    也可以看看:
  • How to use the Pin struct with self-referential structures?
  • 关于rust - 为什么我不能在同一个结构中存储一个值和对该值的引用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32300132/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com