如何在Rust no_std环境下用const泛型长度初始化数组容器
在no_std环境下用const泛型创建元素由const初始化函数生成的容器
我想要创建一个包含const数组的容器,数组的每个元素通过const初始化函数生成。固定大小(用整数字面量指定)的数组已有解决方案,但这里的特殊之处在于数组长度是const泛型,且需要在no_std(仅依赖core库)环境下实现。
问题演示代码
// 注意:Element 不是 Copy 类型 #[derive(Debug)] struct Element(usize); impl Element { pub const fn array_initializer(pos: usize) -> Self { Element(pos) } } #[derive(Debug)] pub struct Container<const N: usize> { data: [Element; N], } impl<const N: usize> Container<N> { pub const fn new() -> Self { // 只有这个函数的内容可以修改! // 任务:创建包含N个元素的容器, // 每个元素通过 Element::array_initializer 初始化 } } static STATIC_CONTAINER: Container<5> = Container::new(); fn main() { println!("{:?}", STATIC_CONTAINER); }
期望输出
Container { data: [Element(0), Element(1), Element(2), Element(3), Element(4)] }
当然,当前Rust版本可能完全无法实现这个需求,但我希望不是这样。我不介意在Container::new中使用unsafe代码,只要代码是sound的。
已尝试的方案
方案1:array-const-fn-init crate
使用该crate尝试初始化数组,但它不支持const泛型数组长度,执行失败。
实现代码:
impl<const N: usize> Container<N> { pub const fn new() -> Self { const fn element_init(pos: usize) -> Element { Element::array_initializer(pos) } let data: [Element; N] = array_const_fn_init::array_const_fn_init!(element_init; N); Self { data } } }
错误信息:
error: proc macro panicked --> src/main.rs:21:34 | 21 | let data: [Element; N] = array_const_fn_init::array_const_fn_init!(element_init; N); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: message: Expected <usize>, found N
方案2:基于官方示例的MaybeUninit实现
参考官方示例用MaybeUninit逐个初始化元素,但转换数组类型时编译器报错。
实现代码:
impl<const N: usize> Container<N> { pub const fn new() -> Self { use core::mem::MaybeUninit; let mut data: [MaybeUninit<Element>; N] = unsafe { MaybeUninit::uninit().assume_init() }; { // const for 循环尚未稳定,因此用while循环 let mut i = 0; while i < N { data[i] = MaybeUninit::new(Element::array_initializer(i)); i += 1; } } let data: [Element; N] = unsafe { core::mem::transmute::<[MaybeUninit<Element>; N], [Element; N]>(data) }; Self { data } } }
错误信息:
error[E0512]: cannot transmute between types of different sizes, or dependently-sized types --> src/main.rs:30:22 | 37 | unsafe { core::mem::transmute::<[MaybeUninit<Element>; N], [Element; N]>(data) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: source type: `[MaybeUninit<Element>; N]` (this type does not have a fixed size) = note: target type: `[Element; N]` (this type does not have a fixed size)
说明:这个错误在技术上不合理,属于编译器限制,未来可能解除;[MaybeUninit<Element>; N]和[Element; N]的内存大小完全一致。
方案3:使用core::intrinsics::transmute_unchecked
已知两种数组大小完全一致,用transmute_unchecked完成类型转换,但需要nightly编译器和feature gate,不符合稳定版需求。
实现代码:
#![feature(core_intrinsics)] // ... impl<const N: usize> Container<N> { pub const fn new() -> Self { use core::mem::MaybeUninit; let mut data: [MaybeUninit<Element>; N] = unsafe { MaybeUninit::uninit().assume_init() }; { // const for 循环尚未稳定,因此用while循环 let mut i = 0; while i < N { data[i] = MaybeUninit::new(Element::array_initializer(i)); i += 1; } } let data: [Element; N] = unsafe { core::intrinsics::transmute_unchecked::<[MaybeUninit<Element>; N], [Element; N]>(data) }; Self { data } } }
说明:这个方案能得到正确结果,但feature(core_intrinsics)可能永远不会进入stable版本。
方案4:使用MaybeUninit::array_assume_init
这个方案逻辑更合理且接近稳定版,但目前仍需要nightly编译器和feature gate。
实现代码:
#![feature(maybe_uninit_array_assume_init)] #![feature(const_maybe_uninit_array_assume_init)] // ... impl<const N: usize> Container<N> { pub const fn new() -> Self { use core::mem::MaybeUninit; let mut data: [MaybeUninit<Element>; N] = unsafe { MaybeUninit::uninit().assume_init() }; { // const for 循环尚未稳定,因此用while循环 let mut i = 0; while i < N { data[i] = MaybeUninit::new(Element::array_initializer(i)); i += 1; } } let data: [Element; N] = unsafe { MaybeUninit::array_assume_init(data) }; Self { data } } }
内容的提问来源于stack exchange,提问作者Finomnis
相关产品推荐
相关产品推荐

