問題
バイト列として読み込んだバッファを、適当な型の配列にしたい、というケースがある。C言語なら有無を言わさずポインタでキャストかけてしまえばいい。例えば、16バイトのバイトバッファを32ビット整数の配列とみなすには以下のように書けばいい。
#include <stdio.h>
#include <stdlib.h>
int main(){
char * buffer = malloc(16);
for (int i = 0; i < 8; i++)
buffer[i] = (char)i;
int32_t *p = (int32_t *)buffer;
printf("%d %d\n", p[0], p[1]);
free(buffer);
}
なんてすっきり。これがRustだとどうなるか。
回答
割に簡単に書ける。
use std::mem::ManuallyDrop;
fn main() {
let mut buffer = Vec::with_capacity(8);
for i in 0..8 {
buffer.push(i as u8);
}
let buffer = ManuallyDrop::new(buffer);
let boxed_array: Box<[i32]> = unsafe {
Box::from_raw(buffer.as_ptr() as *mut [i32; 2])
};
for i in 0..2 {
println!("{:}", boxed_array[i]);
}
println!("{:p}, {:p}", buffer.as_ptr(), boxed_array.as_ptr());
}
バイトバッファは、Vecで表現している。最後の行で同じ領域を指していることを確認している。
ポイントは2つ。
- バイトバッファポインタを取り出して、Box<[i32]> を作る。
- Vecからrawポインタを取り出す。これはsafeで
as_ptr()
で取得できる。 - rawポインタを使ってBoxされた配列を作る。これは当然unsafeなのでunsafeブロック内で、
Box::from_raw
を使う。この際に配列のサイズを指定しなければならない。
- Vecからrawポインタを取り出す。これはsafeで
- これだけだと、
buffer
とvec_int
が同じメモリ領域を参照しているので、ブロックから抜けるときにdouble free になる。- なので、
ManuallyDrop
を使ってbuffer
を ラップしておく。こうすると、buffer
をドロップするときに、メモリを開放しなくなる。所有権がboxed_array
に移ったことになる。
- なので、
実際、ManuallyDrop
の行をコメントアウトすると、
rust_buffer_test(64289,0x203af3ac0) malloc: Double free of object 0x158f04360
rust_buffer_test(64289,0x203af3ac0) malloc: *** set a breakpoint in malloc_error_break to debug
というエラーがでる。
問題点
- Rustの配列は固定長で、コンパイル時に長さが決まっている必要がある。ここでは配列長が2だったが、任意の式をここに書くことはできない。
- Sliceを作ることはできるのだけど、Sliceだと所有権を引き継ぐことができないので、やりたいこととはやや違う。うーん。
- Sliceから
to_vec
でVectorをつくるとコピーしてしまう。通信バッファだと思うとこれはうまくない。 - なにかいい方法がありそうなものだが。直接ポインタからvectorを作る方法がありそうな。。
追記
と思ったら、普通にあった。Vec::from_raw_parts
でよい。この場合も所有権が渡るので ManuallyDrop
は必須。
use std::mem::ManuallyDrop;
use std::vec::Vec;
fn main() {
let mut buffer = Vec::with_capacity(8);
for i in 0..8 {
buffer.push(i as u8);
}
let buffer = ManuallyDrop::new(buffer);
let len = buffer.len() / 4;
let int_vec: Vec<i32> = unsafe {
Vec::from_raw_parts(buffer.as_ptr() as *mut i32, len, len)
};
for i in 0..2 {
println!("{:}", int_vec[i]);
}
println!("{:p}, {:p}", buffer.as_ptr(), int_vec.as_ptr());
}