问题描述
我正在尝试修改一个迫使我学习 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);
}
}
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,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>>,}
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(())
}
解决方法
鉴于代码似乎可以编译,目前尚不清楚问题是什么,但我可以尝试一下:为什么您不能在 into_inner()
中使用 self.wtr
{1}} 函数?
process
获得传递到其 into_inner
参数的 PacketWriter
的所有权。 (你可以这么说是因为 parameter 拼写为 self
,而不是 self
或 &self
。)获得所有权意味着它被消耗了:它不能再被调用者和被调用者负责删除它(阅读:运行析构函数)。取得 &mut self
的所有权后,PacketWriter
函数仅返回 into_inner
字段并删除(运行析构函数)其余字段。但是 wtr
结构体在哪里?它有一个需要包含 Something
的字段,而您只是将其 PacketWriter
拿走并销毁了它!函数结束,PacketWriter
字段中保存的值是未知的:它不可能是从一开始就在那里的东西,因为它被 PacketWriter
接管并销毁了。但它也不能是其他任何东西。
Rust 通常禁止结构具有未初始化或未定义的字段。您需要始终定义该字段。
这是一个有效的例子:
into_inner
(注意:上面的例子逻辑有点模糊。正式地说,因为你不拥有 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
字段并将它回到同一个 PacketWriter::wtr
。您必须提取 PacketWriter
字段并将其放入新的 PacketWriter::wtr
。
这是您可以做到的一种方法。请记住,目标是始终定义 PacketWriter
,因此我们将使用名为 mem::replace
的函数将虚拟 self.packet_wtr
放入 PacketWriter
。这可确保 self.pkt_wtr
中始终包含 something。
self.pkt_wtr
这个解决方案绝对是一个黑客,它可能是一个可以改进借用检查器的地方,以实现暂时未定义的字段是可以的,只要它在再次分配之前不被访问。 (虽然可能有一个我遗漏的边缘情况;一般来说这东西很难推理。)此外,这是一种可以通过稍后编译器通过 dead store elimination 来优化掉的东西。
如果这在分析时被证明是一个热点,则有 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");
}
技术可以允许该字段在该期间无效,但这可能需要一个新问题。
然而,我的建议是找到一种方法将“逃生舱口”功能添加到 unsafe
中,让您可以做您想做的事情:获得对内部 {{1} 的可变引用} 不取得 PacketWriter
的所有权。
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();
}
}
如果您没有线程安全问题,您可以用 Rc 替换 Arc 和用 RefCell 替换 Mutex,在这种情况下,引用访问变为
let data = self.data.borrow_mut();