如何使用 Arc 和 BufReader 编写自引用 Rust 结构?

问题描述 投票:0回答:3

我正在尝试为服务器编写以下代码:

use std::io::{BufReader, BufWriter};
use std::net::TcpStream;

struct User<'a> {
    stream: Arc<TcpStream>,
    reader: BufReader<&'a TcpStream>,
    writer: BufWriter<&'a TcpStream>,
}

fn accept_socket(users: &mut Vec<User>, stream: Arc<TcpStream>) {
    let stream_clone = stream.clone();
    let user = User {
        stream: stream_clone,
        reader: BufReader::new(stream_clone.as_ref()),
        writer: BufWriter::new(stream_clone.as_ref()),
    };
    
    users.push(user);
}

流位于 Arc 后面,因为它是跨线程共享的。 BufReader 和 BufWriter 指向用户自己的 Arc,但是编译器抱怨引用

stream_clone.as_ref()
的生存时间不够长,尽管它显然是这样的(它指向 Arc,只要用户存在,它就不会被删除)活)。如何让编译器接受此代码?

rust self-reference
3个回答
4
投票

自引用结构是不行的。如果结构体被移动,Rust 无法更新引用中的地址,因为移动始终是简单的位复制。与 C++ 及其移动构造函数不同,无法将行为附加到移动。

您可以做的是将

Arc
存储在读取器和写入器中,以便他们共享
TcpStream
的所有权。

struct User {
    stream: Arc<TcpStream>,
    reader: BufReader<IoArc<TcpStream>>,
    writer: BufWriter<IoArc<TcpStream>>,
}

棘手的部分是

Arc
没有实现
Read
Write
。您需要一个能够执行此操作的新类型(
IoArc
,上文)。 Yoshua Wuyts 写了这个问题:

其中一种模式可能鲜为人知,但却是

std
功能的组成部分:
impl Read/Write for &Type
。这意味着,如果您有对 IO 类型的引用,例如
File
TcpStream
,由于一些内部可变性技巧,您仍然可以调用
Read
Write
方法。

这也意味着,如果你想在多个线程之间共享

std::fs::File
,你不需要使用昂贵的
Arc<Mutex<File>>
,因为
Arc<File>
就足够了。

您可能期望,如果我们将 IO 类型

T
包装在
Arc
中,它将实现
Clone + Read + Write
。但实际上它只实现了
Clone + Deref<T>
...然而,这里有一个逃生舱口:我们可以围绕
Arc<T>
创建一个包装类型,通过在内部取消引用
Read + Write
来实现
&T

这是他的解决方案:

/// A variant of `Arc` that delegates IO traits if available on `&T`.
#[derive(Debug)]
pub struct IoArc<T>(Arc<T>);

impl<T> IoArc<T> {
    /// Create a new instance of IoArc.
    pub fn new(data: T) -> Self {
        Self(Arc::new(data))
    }
}

impl<T> Read for IoArc<T>
where
    for<'a> &'a T: Read,
{
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        (&mut &*self.0).read(buf)
    }
}

impl<T> Write for IoArc<T>
where
    for<'a> &'a T: Write,
{
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        (&mut &*self.0).write(buf)
    }

    fn flush(&mut self) -> io::Result<()> {
        (&mut &*self.0).flush()
    }
}

麻省理工学院许可证

IoArc
可在
io_arc
箱中使用,但如果您不想引入依赖项,它足够短,可以自己实现。


1
投票

简单的答案:你不能。

在 Rust 中,每种类型都可以通过 memcpy 隐式移动。因此,如果您的类型存储对其自身的引用,那么一旦发生移动,它就会中断;参考文献将悬空。

更复杂的答案:你不能,除非你使用

Pin
unsafe
和原始指针。

但我很确定,对所有事情都使用

Arc
才是正确的选择。

Arc<TcpStream>
未实现
Read
Write

您可以在

Arc<TcpStream>
周围编写一个非常薄的包装结构,它实现
Read
Write
。应该很容易。

编辑: 看看 @JohnKugelman 的 anwser 这样的包装器。


0
投票

要拥有一个引用自身的结构,Pin 文档有很好的解释和示例:

https://doc.rust-lang.org/std/pin/index.html#example-self-referential-struct

总结一下:

  • Pin 告诉编译器你的对象不能被移动——因此你需要从一开始就将它装箱(并固定它)
  • 您需要使用指针——这是告诉编译器忘记引用的借用规则的方法。最初,指针是
    dangling
    ——但它会在你的结构启动后立即设置
  • 需要使用不安全,但无需支付任何性能代价(与使用安全替代选项时相反>)。

该文档包含对删除的进一步考虑以及构建循环列表的示例。

© www.soinside.com 2019 - 2024. All rights reserved.