使用unsafe代码将bytes::BytesMut转换为bytes::Bytes(不调用freeze)是否安全?
兄弟,直接给你拍板:这种做法绝对不安全,哪怕你的测试跑了1000次都没问题,生产环境里迟早会给你捅出大娄子——而且是那种凌晨3点报警、查半天找不到原因的致命bug。
咱们先掰扯清楚Bytes::from_static到底是干嘛的:这个API的设计意图,是接收一个指向静态内存的切片——也就是那种从程序启动到结束都不会被释放、不会被修改的内存(比如字符串字面量b"hello")。但你的BytesMut是动态分配的内存(要么在堆上,要么如果容量小的话在栈上),完全不符合这个要求。
你觉得“我保证不修改buf直到下一次循环”就没事?但Bytes的语义是共享所有权、线程安全的——你把这个unsafe_bytes传给func_1、func_2,谁能保证这些函数不会把它存到某个长期存在的地方?比如缓存、全局状态、或者某个异步任务的捕获变量里?只要有一次这种情况,下一次循环你buf.clear()或者写入新数据的时候,那些留存的Bytes就会指向被篡改的内存,轻则读到乱码,重则直接触发内存越界、程序崩溃,甚至出现安全漏洞。
你的测试看起来没问题,只是因为测试里unsafe_bytes在当前循环迭代就被用完了,没有跨迭代存活。但生产环境的代码复杂度远高于测试,只要有一个地方不小心把这个unsafe_bytes保存下来——比如你代码里的tokio::spawn要是误写用了unsafe_bytes而不是request_clone——那异步任务里的Bytes就会在后续循环中看到被修改的内存,直接炸锅。
给你几个安全的替代方案
其实你完全不用碰unsafe就能满足需求:
- 最直接的安全方案:用
Bytes::copy_from_slice(&buf[..len])。虽然这会有一次内存复制,但只复制实际收到的len字节数据,比buf.clone().freeze()要高效(后者会复制整个BytesMut的容量,包括未使用的部分)。而且完全安全,不用担心内存被篡改的问题。 - 长期最优方案:修改依赖
Bytes的函数,让它们接受&[u8]。你说不想重构,但这是从根源上解决问题的办法——毕竟你只是需要只读访问数据,&[u8]完全能满足,还能避免不必要的内存分配。 - 折中方案:如果实在不想分配也不想重构,可以用
Arc<BytesMut>配合互斥锁,但这会引入同步开销,可能得不偿失,不如直接用复制方案来得简单。
另外你提到buf.clone().freeze()不能编译?其实是可以的,只是它会复制整个BytesMut的内容(包括未使用的容量),所以才会显得浪费。而Bytes::copy_from_slice(&buf[..len])只复制实际用到的部分,是更优的选择。
备注:内容来源于stack exchange,提问作者Tono Nam

