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

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

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)
}
有时候,我甚至没有取值的引用,我得到 同样的错误:< / p >
struct Combined<'a>(Parent, Child<'a>);


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


Combined(parent, child)
}

在每种情况下,我得到一个错误,其中一个值“does” 活得不够长”。这个错误意味着什么?< / p >

50571 次浏览

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

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

这正是一生中所防止的问题。一生就是一个 一个元数据,允许你和编译器知道a的长度 值将在其当前内存位置处有效。这是一个 重要的区别,因为这是Rust新手常犯的错误。 Rust的生存期为,即对象从 创造和毁灭!< / p > 打个比方,这样想:在一个人的一生中,他们会 居住在许多不同的地方,每个都有一个不同的地址。一个 Rust的生命周期与你的地址目前居住在有关, 不是关于你将来什么时候会死(虽然也会死) 更改您的地址)。你的每一次移动都是相关的,因为你的

.地址不再有效 同样重要的是要注意lifetimes 改变你的代码;你的 代码控制生命期,你的生命期不能控制代码。的 简明的说法是“生命是描述性的,而不是规定性的”。< p> 让我们用一些行将使用的行号来注释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时,会出现问题。这将 “over-extend"生命周期超出其自然长度

这个新知识可以解释前两个例子。第三个 我们需要看看Parent::child的实现。机会 是,它将看起来像这样:

impl Parent {
fn child(&self) -> Child { /* ... */ }
}

使用一生中省略来避免显式写入泛型 < / em >一生参数。它相当于:

impl Parent {
fn child<'a>(&'a self) -> Child<'a> { /* ... */ }
}
在这两种情况下,方法都说Child结构体将是 的具体生命周期参数化后返回的 self。换句话说,Child实例包含一个引用 到创建它的Parent,因此不能存在更长的时间 Parent实例。< / p > 这也让我们认识到我们的身体确实出了问题 功能:创建< / p >
fn make_combined<'a>() -> Combined<'a> { /* ... */ }

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

impl<'a> Combined<'a> {
fn new() -> Combined<'a> { /* ... */ }
}
在这两种情况下,都没有通过参数提供生存期参数 论点。这意味着Combined的生存期 Parameterized with不受任何约束-它可以是任何东西 调用者希望它是。这是荒谬的,因为调用者 可以指定'static生命期,没有办法满足吗 条件。< / p >

我该怎么解决呢?

最简单和最推荐的解决方案是不尝试放 这些项目在同一结构中一起。这样做,你的 结构嵌套将模拟代码的生命周期。地方类型 一起拥有数据到一个结构中,然后提供方法 允许您根据需要获取引用或包含引用的对象 有一个特殊的情况,生命周期跟踪是过度热心的: 当你在堆上放了东西。当您使用 例如,Box<T>。在这种情况下,是被移动的结构 包含指向堆的指针。指向值将保持不变 稳定,但是指针本身的地址会移动。在实践中, 这没关系,因为你总是遵循指针 一些板条箱提供了表示这种情况的方法,但是它们 要求基地址从来没有动.;这就排除了变异的可能性 向量,这可能导致重新分配和移动 堆上分配值。< / p >
  • 租赁(不再维护或支持)
  • owning_ref(有多重稳健性问题)
  • < a href = " https://crates。io /箱/大毒蛇noreferrer“rel = >大毒蛇< / >
  • < a href = " https://crates。io /箱/ self_cell noreferrer“rel = > self_cell < / >

租赁解决问题的例子:

在其他情况下,你可能希望转移到某种类型的引用计数,例如通过使用RcArc

更多的信息

在将parent移到结构体中之后,为什么编译器不能获得对parent的新引用并将其赋值给结构体中的child ?

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

let a = Object::new();
let b = a;
let c = b;

对于每一个 move,程序员不会强制这样做,而是通过创建只在调用时才接受适当引用的方法来获得选择

具有自身引用的类型

有一种特殊的情况,你可以创建一个带有自身引用的类型。不过,你需要使用类似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);
}

参见:

Pin呢?

在Rust 1.33中稳定的Pin有这个在模块文档中:

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

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

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

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

Pin向表中添加的唯一东西是一种常用的方式来声明给定的值保证不移动。

参见:

引起类似编译器消息的一个稍微不同的问题是对象生命周期依赖,而不是存储显式引用。ssh2库就是一个例子。当开发比测试项目更大的项目时,尝试将从会话中获得的SessionChannel放在一个结构体中,对用户隐藏实现细节是很有吸引力的。然而,请注意,Channel定义的类型注释中有'sess生命期,而Session没有。

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

一种非常简单的解决方法是在调用者外部声明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
}
}

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

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

这个会话不拥有所提供的套接字,它是 建议确保套接字在此生命周期内持续存在

.会话,确保通信正常进行

还强烈建议不使用所提供的流 在本届会议期间,视情况同时在其他地方 干扰协议

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

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

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

弧:

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变体),因为在我的情况下,"parent"对象只是一些临时的东西(一个数据库"客户端"对象),我必须构建,以传递给“孩子”;对象(数据库"事务"所以我可以运行一些数据库命令。

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

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

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

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

  • 调用者只需要"child"对象返回。
  • 但是被调用的函数需要构造一个&;parent&;对象来执行其功能。
  • 而借款规则要求“父母”;对象被存储在“make_parent_and_child"函数。(在我的例子中,这是一个start_transaction函数)