结构中的 Rust 结构:借用、生命周期、泛型类型和更多的混乱

问题描述

我正在尝试修改一个迫使我学习 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,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

Rust Playground

这个解决方案绝对是一个黑客,它可能是一个可以改进借用检查器的地方,以实现暂时未定义的字段是可以的,只要它在再次分配之前不被访问。 (虽然可能有一个我遗漏的边缘情况;一般来说这东西很难推理。)此外,这是一种可以通过稍后编译器通过 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();