如何无成本创建未初始化Vec?规避未定义行为方案问询
Rust:无成本读取大二进制文件到Vec的合规实现
问题背景
你当前的代码通过Vec::with_capacity()后直接unsafe { set_len() }创建未初始化Vec,这会触发Clippy警告且属于Rust官方明确指出的未定义行为(UB)。由于Vec容量巨大,无法承担初始化成本,需要一种无成本且合规的实现方式。
原问题代码
lazy_static! { /// Map from perfect hash to equivalence class pub static ref OJP_HIGH_MP5_TABLE_1: Vec<u16> = { let mut path: PathBuf = cargo_home().unwrap(); path.push("onejoker"); path.push("ojp_high_mp5_table_1.bin.gz"); let file = fs::File::open(&path).unwrap(); let mut reader = BufReader::new(file); let mut decoder = GzDecoder::new(&mut reader); let mut bytes: Vec<u8> = Vec::with_capacity((MP5_SIZE + 1) * 2); #[allow(clippy::uninit_vec)] unsafe { bytes.set_len((MP5_SIZE + 1) * 2) }; let mut buf: [MaybeUninit<u8>; IO_BUF_SIZE] = unsafe { MaybeUninit::uninit().assume_init() }; let mut start: usize = 0; loop { let n = decoder.read( unsafe { &mut *(buf.as_mut_ptr() as *mut [u8; IO_BUF_SIZE]) } ).unwrap(); if n == 0 { break; } bytes[start..start + n].copy_from_slice( unsafe { &*(buf.as_ptr() as *const [u8; IO_BUF_SIZE]) }[..n].as_ref() ); start += n; } assert_eq!(start, (MP5_SIZE + 1) * 2); let len = bytes.len() / 2; let capacity = bytes.capacity() / 2; let ptr = bytes.as_mut_ptr() as *mut u16; std::mem::forget(bytes); // prevents deallocation of the vec unsafe { Vec::from_raw_parts(ptr, len, capacity) }; };
合规无成本实现方案
核心思路是用Vec<MaybeUninit<u8>>明确标记未初始化内存,避免UB,同时完全跳过初始化成本:
use std::mem::MaybeUninit; use std::io::{self, Read}; use flate2::read::GzDecoder; use std::path::PathBuf; use lazy_static::lazy_static; use cargo_home::cargo_home; // 替换为你的实际常量值 const MP5_SIZE: usize = 1024 * 1024; const IO_BUF_SIZE: usize = 4096; lazy_static! { /// Map from perfect hash to equivalence class pub static ref OJP_HIGH_MP5_TABLE_1: Vec<u16> = { let mut path: PathBuf = cargo_home().unwrap(); path.push("onejoker"); path.push("ojp_high_mp5_table_1.bin.gz"); let file = std::fs::File::open(&path).unwrap(); let mut reader = io::BufReader::new(file); let mut decoder = GzDecoder::new(&mut reader); let total_bytes = (MP5_SIZE + 1) * 2; // 创建未初始化的Vec,无初始化成本 let mut bytes: Vec<MaybeUninit<u8>> = Vec::with_capacity(total_bytes); // 扩展Vec到目标长度,仅修改len属性,不初始化元素 bytes.resize(total_bytes, MaybeUninit::uninit()); let mut buf: [MaybeUninit<u8>; IO_BUF_SIZE] = MaybeUninit::uninit().assume_init(); let mut start = 0; loop { // 安全读取到缓冲区:read只会写入有效字节,后续只处理读取到的部分 let n = decoder.read(unsafe { &mut *(buf.as_mut_ptr() as *mut [u8; IO_BUF_SIZE]) }).unwrap(); if n == 0 { break; } // 将读取到的字节写入未初始化Vec,确保每个元素被正确初始化 for i in 0..n { bytes[start + i].write(buf[i].assume_init()); } start += n; } assert_eq!(start, total_bytes); // 将Vec<MaybeUninit<u8>>转换为Vec<u8>:此时所有元素已初始化,操作安全 let bytes: Vec<u8> = unsafe { let ptr = bytes.as_mut_ptr() as *mut u8; let len = bytes.len(); let cap = bytes.capacity(); std::mem::forget(bytes); // 避免原Vec的析构函数释放内存 Vec::from_raw_parts(ptr, len, cap) }; // 转换为Vec<u16>:确保文件字节序与目标平台匹配,这里假设是原生字节序 let len = bytes.len() / 2; let capacity = bytes.capacity() / 2; let ptr = bytes.as_mut_ptr() as *mut u16; std::mem::forget(bytes); unsafe { Vec::from_raw_parts(ptr, len, capacity) } }; }
关键说明
- 避免UB的核心:
MaybeUninit明确告知编译器内存未初始化,后续通过write方法逐个初始化元素,完全符合Rust内存安全规则。 - 零初始化成本:
Vec::with_capacity()+resize()仅调整Vec的长度属性,不会对内存执行任何写入操作,完全无初始化开销。 - 类型转换安全:转换为
Vec<u8>和Vec<u16>时,已确保所有内存都被正确写入,且内存布局兼容,操作安全。
内容的提问来源于stack exchange,提问作者Lee Daniel Crocker
相关产品推荐
相关产品推荐

