Rust struct within struct:借用、生命周期、泛型类型和更多的混乱
Rust struct within struct: borrowing, lifetime, generic types and more total confusion
我正在尝试修改一个现有的应用程序,它迫使我学习 Rust,这让我很难过(重新制定...)
我想要一个包含两个字段的结构:
pub struct Something<'a> {
pkt_wtr: PacketWriter<&'a mut Vec<u8>>,
buf: Vec<u8>,
}
其中 'buf' 将用作 PacketWriter 写入其结果的 io。所以 PacketWriter 就像
use std::io::{self};
pub struct PacketWriter<T :io::Write> {
wtr :T,
}
impl <T :io::Write> PacketWriter<T> {
pub fn new(wtr :T) -> Self {
return PacketWriter {
wtr,
};
}
pub fn into_inner(self) -> T {
self.wtr
}
pub fn write(&mut self) {
self.wtr.write_all(&[10,11,12]).unwrap();
println!("wrote packet");
}
}
然后在里面 'Something' 我想这样使用 PacketWriter:让它在 'buf' 中写入它需要的东西,然后把它逐块排出。
impl Something<'_> {
pub fn process(&mut self) {
self.pkt_wtr.write();
let c = self.buf.drain(0..1);
}
}
似乎不可能的是为 'Something'
创建一个可行的构造函数
impl Something<'_> {
pub fn new() -> Self {
let mut buf = Vec::new();
let pkt_wtr = PacketWriter::new(&mut buf);
return Something {
pkt_wtr: pkt_wtr,
buf: buf,
};
}
}
似乎不可行的是,但我尝试在从 'buf' 借来的引用上构建 PacketWriter,而 'buf' 也存储在 'Something' 对象中.
我可以将 'buf' 完全提供给 'PacketWriter'(根据下面的示例),但我以后无法访问 'buf' 的内容。我知道它在下面的示例中有效,但这是因为在将 'buf' 提供给“PacketWriter”(通过 'wtr')之后我可以访问它。实际上,'PacketWriter' 具有该字段 (wtr) 私有,此外,它是我无法修改的代码,例如,为 'wtr'
获取 getter
谢谢
我写了一个小的工作程序来描述意图和问题,有两个选项
use std::io::{self};
pub struct PacketWriter<T :io::Write> {
wtr :T,
}
impl <T :io::Write> PacketWriter<T> {
pub fn new(wtr :T) -> Self {
return PacketWriter {
wtr,
};
}
pub fn into_inner(self) -> T {
self.wtr
}
pub fn write(&mut self) {
self.wtr.write_all(&[10,11,12]).unwrap();
println!("wrote packet");
}
}
/*
// that does not work of course because buf is local but this is not the issue
pub struct Something<'a> {
pkt_wtr: PacketWriter<&'a mut Vec<u8>>,
buf: Vec<u8>,
}
impl Something<'_> {
pub fn new() -> Self {
let mut buf = Vec::new();
let pkt_wtr = PacketWriter::new(&mut buf);
//let mut pkt_wtr = PacketWriter::new(buf);
return Something {
pkt_wtr,
buf,
};
}
pub fn process(&mut self) {
self.pkt_wtr.write();
println!("process {:?}", self.buf);
}
}
*/
pub struct Something {
pkt_wtr: PacketWriter<Vec<u8>>,
}
impl Something {
pub fn new() -> Self {
let pkt_wtr = PacketWriter::new(Vec::new());
return Something {
pkt_wtr,
};
}
pub fn process(&mut self) {
self.pkt_wtr.write();
let file = &mut self.pkt_wtr.wtr;
println!("processing Something {:?}", file);
let c = file.drain(0..1);
println!("Drained {:?}", c);
}
}
fn main() -> std::io::Result<()> {
let mut file = Vec::new();
let mut wtr = PacketWriter::new(&mut file);
wtr.write();
println!("Got data {:?}", file);
{
let c = file.drain(0..2);
println!("Drained {:?}", c);
}
println!("Remains {:?}", file);
let mut data = Something::new();
data.process();
Ok(())
}
考虑到代码似乎可以编译,目前还不完全清楚问题是什么,但我可以试一试:为什么不能在 self.wtr
内部使用 into_inner()
process
函数?
into_inner
获得传递到其 self
参数的 PacketWriter
的所有权。 (你可以看出这一点,因为 parameter 拼写为 self
,而不是 &self
或 &mut self
。)取得所有权意味着它被消耗了:它不能再被使用调用者和被调用者负责删除它(阅读:运行 析构函数)。在获得 PacketWriter
的所有权后,into_inner
函数 returns 仅 wtr
字段并删除(运行析构函数)其余部分。但是 Something
结构在哪里呢?它有一个字段需要包含一个PacketWriter
,而你只是把它的PacketWriter
拿走了并销毁了它!函数结束,PacketWriter
字段中保存的值是未知的:它不可能是从一开始就在那里的东西,因为它已被 into_inner
接管并销毁。但也不能是别的。
Rust 通常禁止结构具有未初始化或未定义的字段。您需要始终定义该字段。
这是工作示例:
pub fn process(&mut self) {
self.pkt_wtr.write();
// There's a valid PacketWriter in pkt_wtr
let raw_wtr: Vec<u8> = self.pkt_wtr.into_inner();
// The PacketWriter in pkt_wtr was consumed by into_inner!
// We have a raw_wtr of type Vec<u8>, but that's not the right type for pkt_wtr
// We could try to call this function here, but what would it do?
self.pkt_wtr.write();
println!("processing Something");
}
(注意:上面的例子逻辑有点模糊。形式上,因为你不拥有 self
,你不能做任何会取得它任何部分所有权的事情,即使你把当你完成后,一切都整齐地回来了。)
您有几个选项可以解决此问题,但有一个主要警告:使用您描述的 public 界面,无法 访问PacketWriter::wtr
字段并将其放回相同的 PacketWriter
。您必须提取 PacketWriter::wtr
字段并将其放入新的 PacketWriter
.
这是您可以做到的一种方法。请记住,我们的目标是始终定义 self.packet_wtr
,因此我们将使用名为 mem::replace
的函数将虚拟 PacketWriter
放入 self.pkt_wtr
。这确保了 self.pkt_wtr
中总是有 something。
pub fn process(&mut self) {
self.pkt_wtr.write();
// Create a new dummy PacketWriter and swap it with self.pkt_wtr
// Returns an owned version of pkt_wtr that we're free to consume
let pkt_wtr_owned = std::mem::replace(&mut self.pkt_wtr, PacketWriter::new(Vec::new()));
// Consume pkt_wtr_owned, returning its wtr field
let raw_wtr = pkt_wtr_owned.into_inner();
// Do anything you want with raw_wtr here -- you own it.
println!("The vec is: {:?}", &raw_wtr);
// Create a new PacketWriter with the old PacketWriter's buffer.
// The dummy PacketWriter is dropped here.
self.pkt_wtr = PacketWriter::new(raw_wtr);
println!("processing Something");
}
这个解决方案绝对是一个 hack,它可能是一个可以改进借用检查器的地方,以意识到暂时保留一个未定义的字段是好的,只要它在再次分配之前没有被访问。 (虽然我可能错过了一个边缘案例;一般来说这些东西很难推理。)此外,这种东西可以被以后的编译器通过 dead store elimination.[=48= 优化掉。 ]
如果在分析时发现这是一个热点,则有 unsafe
技术可以使该字段在此期间无效,但这可能需要一个新问题。
但是,我的建议是找到一种方法,将“逃生舱口”功能添加到 PacketWriter
,让您可以完全按照自己的意愿行事:获取对内部 wtr
没有取得 PacketWriter
.
的所有权
impl<T: io::Write> PacketWriter<T> {
pub fn inner_mut(&mut self) -> &mut T {
&mut self.wtr
}
}
为了澄清,我找到了使用 Rc+RefCell 或 Arc+Mutex 的解决方案。我将缓冲区封装在一个 Rc/RefCell 中并添加了一个 Write
pub struct WrappedWriter {
data :Arc<Mutex<Vec<u8>>>,
}
impl WrappedWriter {
pub fn new(data : Arc<Mutex<Vec<u8>>>) -> Self {
return WrappedWriter {
data,
};
}
}
impl Write for WrappedWriter {
fn write(&mut self, buf: &[u8]) -> Result<usize, Error> {
let mut data = self.data.lock().unwrap();
data.write(buf)
}
fn flush(&mut self) -> Result<(), Error> {
Ok(())
}
}
pub struct Something {
wtr: PacketWriter<WrappedWriter>,
data : Arc<Mutex<Vec<u8>>>,
}
impl Something {
pub fn new() -> Result<Self, Error> {
let data :Arc<Mutex<Vec<u8>>> = Arc::new(Mutex::new(Vec::new()));
let wtr = PacketWriter::new(WrappedWriter::new(Arc::clone(&data)));
return Ok(PassthroughDecoder {
wtr,
data,
});
}
pub fn process(&mut self) {
let mut data = self.data.lock().unwrap();
data.clear();
}
}
如果没有线程安全问题,您可以将 Arc 替换为 Rc,将 Mutex 替换为 RefCell,在这种情况下,引用访问变为
let data = self.data.borrow_mut();
我正在尝试修改一个现有的应用程序,它迫使我学习 Rust,这让我很难过(重新制定...)
我想要一个包含两个字段的结构:
pub struct Something<'a> {
pkt_wtr: PacketWriter<&'a mut Vec<u8>>,
buf: Vec<u8>,
}
其中 'buf' 将用作 PacketWriter 写入其结果的 io。所以 PacketWriter 就像
use std::io::{self};
pub struct PacketWriter<T :io::Write> {
wtr :T,
}
impl <T :io::Write> PacketWriter<T> {
pub fn new(wtr :T) -> Self {
return PacketWriter {
wtr,
};
}
pub fn into_inner(self) -> T {
self.wtr
}
pub fn write(&mut self) {
self.wtr.write_all(&[10,11,12]).unwrap();
println!("wrote packet");
}
}
然后在里面 'Something' 我想这样使用 PacketWriter:让它在 'buf' 中写入它需要的东西,然后把它逐块排出。
impl Something<'_> {
pub fn process(&mut self) {
self.pkt_wtr.write();
let c = self.buf.drain(0..1);
}
}
似乎不可能的是为 'Something'
创建一个可行的构造函数impl Something<'_> {
pub fn new() -> Self {
let mut buf = Vec::new();
let pkt_wtr = PacketWriter::new(&mut buf);
return Something {
pkt_wtr: pkt_wtr,
buf: buf,
};
}
}
似乎不可行的是,但我尝试在从 'buf' 借来的引用上构建 PacketWriter,而 'buf' 也存储在 'Something' 对象中.
我可以将 'buf' 完全提供给 'PacketWriter'(根据下面的示例),但我以后无法访问 'buf' 的内容。我知道它在下面的示例中有效,但这是因为在将 'buf' 提供给“PacketWriter”(通过 'wtr')之后我可以访问它。实际上,'PacketWriter' 具有该字段 (wtr) 私有,此外,它是我无法修改的代码,例如,为 'wtr'
获取 getter谢谢
我写了一个小的工作程序来描述意图和问题,有两个选项
use std::io::{self};
pub struct PacketWriter<T :io::Write> {
wtr :T,
}
impl <T :io::Write> PacketWriter<T> {
pub fn new(wtr :T) -> Self {
return PacketWriter {
wtr,
};
}
pub fn into_inner(self) -> T {
self.wtr
}
pub fn write(&mut self) {
self.wtr.write_all(&[10,11,12]).unwrap();
println!("wrote packet");
}
}
/*
// that does not work of course because buf is local but this is not the issue
pub struct Something<'a> {
pkt_wtr: PacketWriter<&'a mut Vec<u8>>,
buf: Vec<u8>,
}
impl Something<'_> {
pub fn new() -> Self {
let mut buf = Vec::new();
let pkt_wtr = PacketWriter::new(&mut buf);
//let mut pkt_wtr = PacketWriter::new(buf);
return Something {
pkt_wtr,
buf,
};
}
pub fn process(&mut self) {
self.pkt_wtr.write();
println!("process {:?}", self.buf);
}
}
*/
pub struct Something {
pkt_wtr: PacketWriter<Vec<u8>>,
}
impl Something {
pub fn new() -> Self {
let pkt_wtr = PacketWriter::new(Vec::new());
return Something {
pkt_wtr,
};
}
pub fn process(&mut self) {
self.pkt_wtr.write();
let file = &mut self.pkt_wtr.wtr;
println!("processing Something {:?}", file);
let c = file.drain(0..1);
println!("Drained {:?}", c);
}
}
fn main() -> std::io::Result<()> {
let mut file = Vec::new();
let mut wtr = PacketWriter::new(&mut file);
wtr.write();
println!("Got data {:?}", file);
{
let c = file.drain(0..2);
println!("Drained {:?}", c);
}
println!("Remains {:?}", file);
let mut data = Something::new();
data.process();
Ok(())
}
考虑到代码似乎可以编译,目前还不完全清楚问题是什么,但我可以试一试:为什么不能在 self.wtr
内部使用 into_inner()
process
函数?
into_inner
获得传递到其 self
参数的 PacketWriter
的所有权。 (你可以看出这一点,因为 parameter 拼写为 self
,而不是 &self
或 &mut self
。)取得所有权意味着它被消耗了:它不能再被使用调用者和被调用者负责删除它(阅读:运行 析构函数)。在获得 PacketWriter
的所有权后,into_inner
函数 returns 仅 wtr
字段并删除(运行析构函数)其余部分。但是 Something
结构在哪里呢?它有一个字段需要包含一个PacketWriter
,而你只是把它的PacketWriter
拿走了并销毁了它!函数结束,PacketWriter
字段中保存的值是未知的:它不可能是从一开始就在那里的东西,因为它已被 into_inner
接管并销毁。但也不能是别的。
Rust 通常禁止结构具有未初始化或未定义的字段。您需要始终定义该字段。
这是工作示例:
pub fn process(&mut self) {
self.pkt_wtr.write();
// There's a valid PacketWriter in pkt_wtr
let raw_wtr: Vec<u8> = self.pkt_wtr.into_inner();
// The PacketWriter in pkt_wtr was consumed by into_inner!
// We have a raw_wtr of type Vec<u8>, but that's not the right type for pkt_wtr
// We could try to call this function here, but what would it do?
self.pkt_wtr.write();
println!("processing Something");
}
(注意:上面的例子逻辑有点模糊。形式上,因为你不拥有 self
,你不能做任何会取得它任何部分所有权的事情,即使你把当你完成后,一切都整齐地回来了。)
您有几个选项可以解决此问题,但有一个主要警告:使用您描述的 public 界面,无法 访问PacketWriter::wtr
字段并将其放回相同的 PacketWriter
。您必须提取 PacketWriter::wtr
字段并将其放入新的 PacketWriter
.
这是您可以做到的一种方法。请记住,我们的目标是始终定义 self.packet_wtr
,因此我们将使用名为 mem::replace
的函数将虚拟 PacketWriter
放入 self.pkt_wtr
。这确保了 self.pkt_wtr
中总是有 something。
pub fn process(&mut self) {
self.pkt_wtr.write();
// Create a new dummy PacketWriter and swap it with self.pkt_wtr
// Returns an owned version of pkt_wtr that we're free to consume
let pkt_wtr_owned = std::mem::replace(&mut self.pkt_wtr, PacketWriter::new(Vec::new()));
// Consume pkt_wtr_owned, returning its wtr field
let raw_wtr = pkt_wtr_owned.into_inner();
// Do anything you want with raw_wtr here -- you own it.
println!("The vec is: {:?}", &raw_wtr);
// Create a new PacketWriter with the old PacketWriter's buffer.
// The dummy PacketWriter is dropped here.
self.pkt_wtr = PacketWriter::new(raw_wtr);
println!("processing Something");
}
这个解决方案绝对是一个 hack,它可能是一个可以改进借用检查器的地方,以意识到暂时保留一个未定义的字段是好的,只要它在再次分配之前没有被访问。 (虽然我可能错过了一个边缘案例;一般来说这些东西很难推理。)此外,这种东西可以被以后的编译器通过 dead store elimination.[=48= 优化掉。 ]
如果在分析时发现这是一个热点,则有 unsafe
技术可以使该字段在此期间无效,但这可能需要一个新问题。
但是,我的建议是找到一种方法,将“逃生舱口”功能添加到 PacketWriter
,让您可以完全按照自己的意愿行事:获取对内部 wtr
没有取得 PacketWriter
.
impl<T: io::Write> PacketWriter<T> {
pub fn inner_mut(&mut self) -> &mut T {
&mut self.wtr
}
}
为了澄清,我找到了使用 Rc+RefCell 或 Arc+Mutex 的解决方案。我将缓冲区封装在一个 Rc/RefCell 中并添加了一个 Write
pub struct WrappedWriter {
data :Arc<Mutex<Vec<u8>>>,
}
impl WrappedWriter {
pub fn new(data : Arc<Mutex<Vec<u8>>>) -> Self {
return WrappedWriter {
data,
};
}
}
impl Write for WrappedWriter {
fn write(&mut self, buf: &[u8]) -> Result<usize, Error> {
let mut data = self.data.lock().unwrap();
data.write(buf)
}
fn flush(&mut self) -> Result<(), Error> {
Ok(())
}
}
pub struct Something {
wtr: PacketWriter<WrappedWriter>,
data : Arc<Mutex<Vec<u8>>>,
}
impl Something {
pub fn new() -> Result<Self, Error> {
let data :Arc<Mutex<Vec<u8>>> = Arc::new(Mutex::new(Vec::new()));
let wtr = PacketWriter::new(WrappedWriter::new(Arc::clone(&data)));
return Ok(PassthroughDecoder {
wtr,
data,
});
}
pub fn process(&mut self) {
let mut data = self.data.lock().unwrap();
data.clear();
}
}
如果没有线程安全问题,您可以将 Arc 替换为 Rc,将 Mutex 替换为 RefCell,在这种情况下,引用访问变为
let data = self.data.borrow_mut();