let receiver: Receiver = ...;
let mut buf = [0; 1024]; // allocate a buf that is definitely too big
let bytes_received = reciever.recv(&mut buf);
这是我见过的一种比较常见的模式。要访问数据,我们可能会做如下操作
let data = buf[0..bytes_received];
这是一个很好的模式,但我的问题是我们总是将整个数组清零,即使我们不需要。这不是一个昂贵的操作,但总是忽略它似乎很奇怪。
我建议的另一个选择是
use std::mem::{MaybeUninit, transmute};
struct Receiver {
// just for demonstration...
data: &'static str,
}
trait Receive {
// this is the only required method, since `recv` can be derived from it
fn recv_maybe_uninit(&mut self, buf: &mut [MaybeUninit<u8>]) -> &[u8];
fn recv(&mut self, buf: &mut [u8]) -> usize {
self.recv_maybe_uninit(unsafe { transmute(buf) }).len()
}
}
impl Receive for Receiver {
fn recv_maybe_uninit(&mut self, buf: &mut [MaybeUninit<u8>]) -> &[u8] {
for (i, byte) in self.data.as_bytes().iter().enumerate() {
buf[i] = MaybeUninit::<u8>::new(*byte);
if i == buf.len() {
return unsafe { transmute(buf) }
}
}
unsafe { transmute(&buf[0..self.data.as_bytes().len()]) }
}
}
fn main() {
let mut receiver = Receiver {
data: "Hello, world!",
};
let mut buf = [MaybeUninit::uninit(); 1024];
let data = receiver.recv_maybe_uninit(&mut buf);
println!("{:?}", std::str::from_utf8(data).unwrap());
}
如果recv_maybe_uninit
是默认值,我们可以很容易地推导出recv
!
也许更好的实现是让缓冲区实现一个trait,允许向其中写入u8
...
不管怎么说,为什么不用这种风格呢?
2条答案
按热度按时间vfwfrxfs1#
也许你看到
&mut [u8]
用于这种接口的最大原因是因为这是标准库所做的。标准库不使用
&[MaybeUninit<u8>]
的原因是MaybeUninit
在1.36.0版本之前并不存在,而Read
在1.0.0版本中可用。但是这需要一个默认的实现来初始化未初始化的数组,这样它就可以被传递给read
/read_exact
。另一个原因可能是你几乎是在强迫trait实现者使用
unsafe
来处理MaybeUninit
(或者至少遵从其他使用unsafe
的实现)。如果将来有类似这样的函数,这一点可能会得到改善,但目前还没有安全支持。话虽如此,你想要的 * 是 * 在其他地方完成的,比如时雄的
AsyncRead
trait,它没有直接使用&mut [MaybeUninit<u8>]
,而是一个ReadBuf
,它是一个安全的 Package 器,包裹着一个部分初始化的字节切片,在我看来,这给了你两个世界中最好的。有一个RFC将这种实现添加到标准库中:RFC 2930 -读取缓冲区。
我建议在创建一个新的类读API时遵循这种结构。
nfzehxib2#
首先,
MaybeUninit
is quite new。特别是对于数组来说,大多数API仍然是每晚的。这些API应该随着rust版本的增加而改变吗?我不认为这是理想的。其次,如果图书馆用户需要这个功能,他们可以自己做得很好。