LoginSignup
0
0

Rust でバッファを型変換

Last updated at Posted at 2024-05-02

問題

バイト列として読み込んだバッファを、適当な型の配列にしたい、というケースがある。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 を使う。この際に配列のサイズを指定しなければならない。
  • これだけだと、buffervec_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());
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0