我正在尝试为服务器编写以下代码:
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 无法更新引用中的地址,因为移动始终是简单的位复制。与 C++ 及其移动构造函数不同,无法将行为附加到移动。
您可以做的是将
Arc
存储在读取器和写入器中,以便他们共享 TcpStream
的所有权。
struct User {
stream: Arc<TcpStream>,
reader: BufReader<IoArc<TcpStream>>,
writer: BufWriter<IoArc<TcpStream>>,
}
棘手的部分是
Arc
没有实现Read
和Write
。您需要一个能够执行此操作的新类型(IoArc
,上文)。 Yoshua Wuyts 写了这个问题:
其中一种模式可能鲜为人知,但却是
功能的组成部分:std
。这意味着,如果您有对 IO 类型的引用,例如impl Read/Write for &Type
或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
箱中使用,但如果您不想引入依赖项,它足够短,可以自己实现。
简单的答案:你不能。
在 Rust 中,每种类型都可以通过 memcpy 隐式移动。因此,如果您的类型存储对其自身的引用,那么一旦发生移动,它就会中断;参考文献将悬空。
更复杂的答案:你不能,除非你使用
Pin
、unsafe
和原始指针。
但我很确定,对所有事情都使用
Arc
才是正确的选择。
未实现Arc<TcpStream>
或Read
Write
您可以在
Arc<TcpStream>
周围编写一个非常薄的包装结构,它实现 Read
和 Write
。应该很容易。
编辑: 看看 @JohnKugelman 的 anwser 这样的包装器。
要拥有一个引用自身的结构,Pin 文档有很好的解释和示例:
https://doc.rust-lang.org/std/pin/index.html#example-self-referential-struct
总结一下:
dangling
——但它会在你的结构启动后立即设置该文档包含对删除的进一步考虑以及构建循环列表的示例。