我有一个值,我想存储这个值和一个引用 在我自己类型的价值中:

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)
}

有时,我甚至不取值的参考,我得到 同样的错误:

struct Combined<'a>(Parent, Child<'a>);

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

    Combined(parent, child)
}

在每一种情况下,我都会得到一个值“does”的错误 活得不够长”。这个错误意味着什么?


当前回答

A slightly different issue which causes very similar compiler messages is object lifetime dependency, rather than storing an explicit reference. An example of that is the ssh2 library. When developing something bigger than a test project, it is tempting to try to put the Session and Channel obtained from that session alongside each other into a struct, hiding the implementation details from the user. However, note that the Channel definition has the 'sess lifetime in its type annotation, while Session doesn't.

这将导致与生命周期相关的类似编译器错误。

一种非常简单的解决方法是在调用者外部声明Session,然后用生命周期注释结构中的引用,类似于Rust用户论坛中讨论的封装SFTP时相同问题的答案。这看起来并不优雅,可能并不总是适用-因为现在您有两个实体要处理,而不是您想要的一个!

事实证明,另一个答案中的租赁板条箱或owning_ref板条箱也是这个问题的解决方案。让我们考虑owning_ref,它有一个特殊的对象来实现这个目的: OwningHandle。为了避免底层对象移动,我们使用Box在堆上分配它,这给了我们以下可能的解决方案:

use ssh2::{Channel, Error, Session};
use std::net::TcpStream;

use owning_ref::OwningHandle;

struct DeviceSSHConnection {
    tcp: TcpStream,
    channel: OwningHandle<Box<Session>, Box<Channel<'static>>>,
}

impl DeviceSSHConnection {
    fn new(targ: &str, c_user: &str, c_pass: &str) -> Self {
        use std::net::TcpStream;
        let mut session = Session::new().unwrap();
        let mut tcp = TcpStream::connect(targ).unwrap();

        session.handshake(&tcp).unwrap();
        session.set_timeout(5000);
        session.userauth_password(c_user, c_pass).unwrap();

        let mut sess = Box::new(session);
        let mut oref = OwningHandle::new_with_fn(
            sess,
            unsafe { |x| Box::new((*x).channel_session().unwrap()) },
        );

        oref.shell().unwrap();
        let ret = DeviceSSHConnection {
            tcp: tcp,
            channel: oref,
        };
        ret
    }
}

这段代码的结果是,我们不能再使用会话,但它与我们将使用的通道一起存储。由于OwningHandle对象在将其存储在结构体中时解引用Box,而Box解引用Channel,因此我们将其命名为Box。注:这只是我的理解。我怀疑这可能是不正确的,因为它似乎非常接近OwningHandle不安全的讨论。

这里有一个奇怪的细节,Session在逻辑上与TcpStream有类似的关系,就像Channel与Session的关系一样,但是它的所有权没有被占用,并且没有类型注释。相反,这是由用户来处理的,正如握手方法的文档所说:

这个会话不拥有所提供的套接字的所有权,而是 建议确保套接字在此生命周期内持续存在 会话,以确保正确执行通信。 还强烈建议不要使用所提供的流 在本届会议期间,视情况同时在其他地方 干扰协议。

因此使用TcpStream时,完全由程序员来确保代码的正确性。对于OwningHandle,使用不安全的{}块绘制“危险魔法”发生的位置。

关于这个问题的进一步和更高级的讨论在Rust用户论坛线程中——其中包括一个不同的例子和它使用租赁板条箱的解决方案,其中不包含不安全的块。

其他回答

我发现Arc(只读)或Arc<Mutex>(带锁的读写)模式有时是性能和代码复杂性(主要是由生命期注释引起的)之间非常有用的权衡。

Arc:

use std::sync::Arc;

struct Parent {
    child: Arc<Child>,
}
struct Child {
    value: u32,
}
struct Combined(Parent, Arc<Child>);

fn main() {
    let parent = Parent { child: Arc::new(Child { value: 42 }) };
    let child = parent.child.clone();
    let combined = Combined(parent, child.clone());

    assert_eq!(combined.0.child.value, 42);
    assert_eq!(child.value, 42);
    // combined.0.child.value = 50; // fails, Arc is not DerefMut
}

Arc + Mutex:

use std::sync::{Arc, Mutex};

struct Child {
    value: u32,
}
struct Parent {
    child: Arc<Mutex<Child>>,
}
struct Combined(Parent, Arc<Mutex<Child>>);

fn main() {
    let parent = Parent { child: Arc::new(Mutex::new(Child {value: 42 }))};
    let child = parent.child.clone();
    let combined = Combined(parent, child.clone());

    assert_eq!(combined.0.child.lock().unwrap().value, 42);
    assert_eq!(child.lock().unwrap().value, 42);
    child.lock().unwrap().value = 50;
    assert_eq!(combined.0.child.lock().unwrap().value, 50);
}

参见RwLock(什么时候或者为什么我应该使用互斥锁而不是RwLock?)

作为Rust的新手,我有一个类似于你上一个例子的情况:

struct Combined<'a>(Parent, Child<'a>);

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

    Combined(parent, child)
}

最后,我用这个模式解决了这个问题:

fn make_parent_and_child<'a>(anchor: &'a mut DataAnchorFor1<Parent>) -> Child<'a> {
    // construct parent, then store it in anchor object the caller gave us a mut-ref to
    *anchor = DataAnchorFor1::holding(Parent::new());

    // now retrieve parent from storage-slot we assigned to in the previous line
    let parent = anchor.val1.as_mut().unwrap();

    // now proceed with regular code, except returning only the child
    // (the parent can already be accessed by the caller through the anchor object)
    let child = parent.child();
    child
}

// this is a generic struct that we can define once, and use whenever we need this pattern
// (it can also be extended to have multiple slots, naturally)
struct DataAnchorFor1<T> {
    val1: Option<T>,
}
impl<T> DataAnchorFor1<T> {
    fn empty() -> Self {
        Self { val1: None }
    }
    fn holding(val1: T) -> Self {
        Self { val1: Some(val1) }
    }
}

// for my case, this was all I needed
fn main_simple() {
    let anchor = DataAnchorFor1::empty();
    let child = make_parent_and_child(&mut anchor);
    let child_processing_result = do_some_processing(child);
    println!("ChildProcessingResult:{}", child_processing_result);
}

// but if access to parent-data later on is required, you can use this
fn main_complex() {
    let anchor = DataAnchorFor1::empty();
    
    // if you want to use the parent object (which is stored in anchor), you must...
    // ...wrap the child-related processing in a new scope, so the mut-ref to anchor...
    // ...gets dropped at its end, letting us access anchor.val1 (the parent) directly
    let child_processing_result = {
        let child = make_parent_and_child(&mut anchor);
        // do the processing you want with the child here (avoiding ref-chain...
        // ...back to anchor-data, if you need to access parent-data afterward)
        do_some_processing(child)
    };

    // now that scope is ended, we can access parent data directly
    // so print out the relevant data for both parent and child (adjust to your case)
    let parent = anchor.val1.unwrap();
    println!("Parent:{} ChildProcessingResult:{}", parent, child_processing_result);
}

这远远不是一个普遍的解决方案!但它适用于我的情况,并且只需要使用上面的main_simple模式(而不是main_complex变体),因为在我的情况下,“父”对象只是一些临时的对象(数据库“Client”对象),我必须构造它来传递给“子”对象(数据库“Transaction”对象),这样我就可以运行一些数据库命令。

无论如何,它完成了我所需要的封装/简化样板(因为我有许多需要创建Transaction/“child”对象的函数,现在它们所需要的只是通用的锚对象创建行),同时避免了使用整个新库的需要。

以下是我所知道的可能相关的库:

owning-ref 租赁 大毒蛇 reffers self_cell 埃舍尔 rust-viewbox

然而,我浏览了它们,它们似乎都有这样或那样的问题(多年未更新,有多个不健全的问题/担忧,等等),所以我犹豫是否使用它们。

因此,虽然这不是一个通用的解决方案,但我想我应该为具有类似用例的人提到它:

调用者只需要返回"child"对象。 但是被调用的函数需要构造一个“父”对象来执行它的函数。 借用规则要求“父”对象存储在“make_parent_and_child”函数之外的地方。(在我的情况下,这是一个start_transaction函数)

A slightly different issue which causes very similar compiler messages is object lifetime dependency, rather than storing an explicit reference. An example of that is the ssh2 library. When developing something bigger than a test project, it is tempting to try to put the Session and Channel obtained from that session alongside each other into a struct, hiding the implementation details from the user. However, note that the Channel definition has the 'sess lifetime in its type annotation, while Session doesn't.

这将导致与生命周期相关的类似编译器错误。

一种非常简单的解决方法是在调用者外部声明Session,然后用生命周期注释结构中的引用,类似于Rust用户论坛中讨论的封装SFTP时相同问题的答案。这看起来并不优雅,可能并不总是适用-因为现在您有两个实体要处理,而不是您想要的一个!

事实证明,另一个答案中的租赁板条箱或owning_ref板条箱也是这个问题的解决方案。让我们考虑owning_ref,它有一个特殊的对象来实现这个目的: OwningHandle。为了避免底层对象移动,我们使用Box在堆上分配它,这给了我们以下可能的解决方案:

use ssh2::{Channel, Error, Session};
use std::net::TcpStream;

use owning_ref::OwningHandle;

struct DeviceSSHConnection {
    tcp: TcpStream,
    channel: OwningHandle<Box<Session>, Box<Channel<'static>>>,
}

impl DeviceSSHConnection {
    fn new(targ: &str, c_user: &str, c_pass: &str) -> Self {
        use std::net::TcpStream;
        let mut session = Session::new().unwrap();
        let mut tcp = TcpStream::connect(targ).unwrap();

        session.handshake(&tcp).unwrap();
        session.set_timeout(5000);
        session.userauth_password(c_user, c_pass).unwrap();

        let mut sess = Box::new(session);
        let mut oref = OwningHandle::new_with_fn(
            sess,
            unsafe { |x| Box::new((*x).channel_session().unwrap()) },
        );

        oref.shell().unwrap();
        let ret = DeviceSSHConnection {
            tcp: tcp,
            channel: oref,
        };
        ret
    }
}

这段代码的结果是,我们不能再使用会话,但它与我们将使用的通道一起存储。由于OwningHandle对象在将其存储在结构体中时解引用Box,而Box解引用Channel,因此我们将其命名为Box。注:这只是我的理解。我怀疑这可能是不正确的,因为它似乎非常接近OwningHandle不安全的讨论。

这里有一个奇怪的细节,Session在逻辑上与TcpStream有类似的关系,就像Channel与Session的关系一样,但是它的所有权没有被占用,并且没有类型注释。相反,这是由用户来处理的,正如握手方法的文档所说:

这个会话不拥有所提供的套接字的所有权,而是 建议确保套接字在此生命周期内持续存在 会话,以确保正确执行通信。 还强烈建议不要使用所提供的流 在本届会议期间,视情况同时在其他地方 干扰协议。

因此使用TcpStream时,完全由程序员来确保代码的正确性。对于OwningHandle,使用不安全的{}块绘制“危险魔法”发生的位置。

关于这个问题的进一步和更高级的讨论在Rust用户论坛线程中——其中包括一个不同的例子和它使用租赁板条箱的解决方案,其中不包含不安全的块。

让我们来看一个简单的实现:

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`

要完全理解这个错误,您必须考虑如何 值在内存中表示,当您移动时会发生什么 这些值。让我们用一些假设来注释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 ... ?

孩子该怎么办?如果值只是像parent一样移动 是吗,那么它会指的记忆,不再是保证吗 其中有一个有效值。允许存储任何其他代码段 内存地址0x1000的值。访问内存,假设它是 整数可能导致崩溃和/或安全漏洞,并且是其中之一 Rust可以防止的主要错误类别。

这正是我们一生所能避免的问题。一生就是一个 一个元数据,允许你和编译器知道a的长度 值在其当前内存位置有效。这是一个 重要的区别,因为这是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结构将被删除 的具体生命周期参数化后返回的 自我。换句话说,Child实例包含一个引用 归于创造它的“父”,因此它不能活得更久 父类实例。

这也让我们认识到,我们的确有问题 创建功能:

fn make_combined<'a>() -> Combined<'a> { /* ... */ }

尽管你更可能看到它以另一种形式写出来:

impl<'a> Combined<'a> {
    fn new() -> Combined<'a> { /* ... */ }
}

在这两种情况下,都没有通过 论点。这意味着组合的生命周期将是 Parameterized with不受任何约束-它可以是任何东西 调用者希望它是。这是荒谬的,因为调用者 可以指定“静态生命周期”,但没有办法满足它 条件。

我该怎么解决呢?

最简单和最推荐的解决方案是不尝试放 这些项目在同一结构中一起。这样做,你的 结构嵌套将模拟代码的生命周期。地方类型 一起拥有数据到一个结构中,然后提供方法 允许您根据需要获取包含引用的引用或对象。

有一个特殊的情况,生命周期跟踪是过度热心的: 当你在堆上放了东西。当您使用 例如,Box<T>。在这种情况下,是被移动的结构 包含指向堆的指针。指向值将保持不变 稳定,但是指针本身的地址会移动。在实践中, 这无关紧要,因为您始终遵循指针。

一些板条箱提供了表示这种情况的方法,但他们 要求基址永不移动。这就排除了变异的可能性 向量,这可能导致重新分配和移动 堆上分配值。

租金(不再维持或支持) Owning_ref(有多个可靠性问题) 大毒蛇 self_cell

租赁解决问题的例子:

是否有String::chars的专有版本? 从方法中独立返回RWLockReadGuard 我如何在Rust中返回一个锁定结构成员的迭代器? 如何返回对互斥量下的值的子值的引用? 我如何存储一个结果使用Serde零拷贝反序列化的未来启用超块? 如何存储一个引用而不必处理生命周期?

在其他情况下,您可能希望使用某种类型的引用计数,例如使用Rc或Arc。

更多的信息

移动父结构后,为什么编译器不能得到一个新的引用的父和分配给子结构?

虽然理论上可以这样做,但这样做会带来大量的复杂性和开销。每次移动对象时,编译器都需要插入代码来“修复”引用。这将意味着复制结构体不再是一个非常廉价的操作,只是移动一些位。它甚至可能意味着这样的代码是昂贵的,这取决于一个假设的优化器有多好:

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);
}

在某种意义上,这确实有效,但所创造的价值受到高度限制——它永远无法移动。值得注意的是,这意味着它不能从函数返回,也不能通过值传递给任何东西。构造函数显示了与上面相同的生命周期问题:

fn creator<'a>() -> WhatAboutThis<'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);
}

参见:

不能在一个代码中多次借用为可变的-但可以在另一个非常相似的代码中借用

Pin呢?

在Rust 1.33中稳定的引脚,在模块文档中有这个:

这种场景的一个主要例子是构建自引用结构,因为移动带有指向自身的指针的对象将使它们失效,这可能导致未定义的行为。

需要注意的是,“自我参照”并不一定意味着使用参照。事实上,一个自引用结构体的例子明确地说(强调我的):

我们不能用正常的引用通知编译器, 因为这种模式不能用通常的借款规则来描述。 相反,我们使用一个原始指针,尽管已知它不是空指针, 因为我们知道它指向弦。

从Rust 1.0开始,就有了使用原始指针的能力。实际上,ownership -ref和rental在底层使用原始指针。

Pin向表中添加的唯一一件事是声明给定值保证不移动的常用方法。

参见:

如何使用自引用结构的针结构?