🦁

T:Defaultじゃない配列をなんとかしてみる

2024/03/28に公開

概略pub fn conv<const N: usize, T, U>(scr: &[T; N], proc: impl Fn(&T) -> U) -> [U; N]のような関数を想定したとき、U:Defaultでは無いのでどうやって配列を生成するか実験していた結果をまとめみようかなって。

中にはunsafeを頻用して言う実装があり、僕もRustの学習途上なのでもし何か問題があるような実装が提示されていた場合はご指摘いただけるとこれ幸いナリ。

正攻法

std::array::from_fn 関数を使おう。そうすれば安全かつ効率的に実現出来る、以下はその例

pub fn conv<const N: usize, T, U>(scr: &[T; N], proc: impl Fn(&T) -> U) -> [U; N] {
	std::array::from_fn(|i| proc(&scr[i]))
}

fn main() {
	let a = [1, 2, 3];
	let b = conv(&a, |i| i.to_string());

	for elem in b.iter() {
		println!("{elem}")
	}
}

以下は上記関数の存在に気付くまで色々と試行錯誤した結果となっている。

MaybeUninitを使う方法

MaybeUninitを使うコトで、名前通り初期化されていない領域を確保が出来る。その後自分の責務に於いて初期化を行い、返却することが出来る。

#![feature(maybe_uninit_uninit_array)]
#![feature(maybe_uninit_array_assume_init)]

use std::mem::MaybeUninit;


pub fn use_maybe_uninit<const N: usize, T, U>(scr: &[T; N], proc: impl Fn(&T) -> U) -> [U; N] {
	let mut arr = MaybeUninit::<U>::uninit_array::<N>();

	unsafe {
		for idx in 0..N {
			arr[idx].write(proc(&scr[idx]));
		}

		return MaybeUninit::<U>::array_assume_init(arr);
	}
}

fn main() {
	let a = [1, 2, 3];
	let b = use_maybe_uninit(&a, |i| i.to_string());

	for elem in b.iter() {
		println!("{elem}")
	}
}

writearray_assume_initがunsafeなのであんまりオススメできない。但し、状態の確認は出来るので後述の方法よっかまだましとも言える

Layoutとallocを使う方法

この方法はunsafe使っている上にallocによって一度Heapに展開したEntityをreadによってStackに再配置しているので効率が悪い上に危険が危ないので全くオススメできる方法ではない。

とはゆえうっかり何かの拍子に必要になるかもしれないのでとりあえず書き残しておく

use std::alloc::{alloc, dealloc, Layout};

pub fn too_danger<const N: usize, T, U>(scr: &[T; N], proc: impl Fn(&T) -> U) -> [U; N] {
	let layout = Layout::array::<U>({ N }).unwrap();

	unsafe {
		let ptr = alloc(layout) as *mut U;

		if ptr.is_null() {
			panic!("Failed to alloc.");
		}

		for idx in 0..N {
			ptr.add(idx).write(proc(&scr[idx]));
		}

		let array = std::ptr::read(ptr as *const [U; N]);

		dealloc(ptr as *mut u8, layout);

		array
	}
}

fn main() {
	
	let a = [1, 2, 3];
	let b = too_danger(&a, |i| i.to_string());

	for elem in b.iter() {
		println!("{elem}")
	}
}

まとめ

Vec<T>を経由してtry_intoすることも出来るので、基本的にunsafeを使うコトなくなんとかした方が絶対幸せになれる。

とは言え万難排してなんとかしなきゃイケないことももしかしたらあるかもしれないのでそのときのための備忘録として書き残しておこうかなって

Discussion