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

标签 rust reference lifetime borrow-checker

我有一个值,我想存储该值和对
在我自己的类型的那个值里面的东西:

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那样移动
是,那么它将指的是不再保证
其中有一个有效值。允许存储任何其他代码
存储器地址0x1000处的值。假设是访问该内存
整数可能会导致崩溃和/或安全漏洞,并且是其中之一
Rust可以防止的主要错误类别。
这正是生命周期可以防止的问题。一生是一个
一点元数据,使您和编译器可以知道
值将在其当前存储位置处有效。那是
重要区别,因为这是Rust新移民经常犯的错误。
rust 生命周期不是物体被腐 eclipse 的时间
创建和何时销毁!
打个比方,这样想:在一个人的一生中,他们会
居住在许多不同的位置,每个位置都有不同的地址。一种
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;
    
    程序员不必强制这种情况发生在每一个 Action 上,而是可以通过创建只在调用它们时才采用适当引用的方法来选择何时发生这种情况。
    引用自身的类型
    在一种特定的情况下,您可以使用对自身的引用来创建类型。但是,您需要使用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);
    }
    
    从某种意义上说,这确实有效,但是所创建的值受到严格限制-永远不能移动。值得注意的是,这意味着它不能从函数返回或按值传递给任何对象。构造函数在生命周期方面显示出与上述相同的问题:
    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呢?
    在Rust 1.33中稳定的 Pin 具有以下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/64546223/

    相关文章:

    rust - Rust 的移位运算符的确切语义是什么?

    rust - Rust 中的共享循环引用

    json - TreeMap<String, String> 到 json

    rest - 无法在 `Fn` 闭包中将捕获的外部变量借用为可变的

    python - 在 Python 中,两个对象何时相同?

    c++ - 当分配给 const 时,C++ 临时的生命周期延长为词法

    php - register_shutdown_function 将引用转换为值?

    javascript - 如何将引用的变量设置为 null?

    c++ - 从类返回不正确的值

    multithreading - 如何将对堆栈变量的引用传递给线程?