LEB128形式の数値をRustで読み取る

WebAssemblyのbinary formatでは、数値の読み込み時にLEB128形式と呼ばれる可変長のフォーマットを使う。以下を参考にして、その関数をRustで書いてみた。ちなみに書籍の中ではRubyで書かれているので、比較のためその部分だけ抜粋する。

booth.pm

Ruby実装(上記書籍より引用)

def read_unsigned_leb128(max_bits)
  value = 0
  shift = 0

  loop do
    b = readbyte
    value |=  ((b & 0x7F) << shift)

    shift += 7
    break if b[7] == 0
    raise "Invalid LEB128 encoding" if shift >= max_bits
  end

  value
end

Rust実装

fn read_u32_from_leb128<T: Read>(reader: &mut BufReader<T>) -> u32 {
    let mut acc: u32 = 0;
    let mut count: u8 = 0;
    for byte in reader.bytes() {
        if let Ok(b) = byte {
            let val: u32 = (b & 0b01111111) as u32;
            let shifted_val = val << (7 * count);
            acc += shifted_val as u32;
            count += 1;
            if b < 0b10000000 { break; }
        } else {
            break;
        }
    }
    acc
}

実装していくつか気づく点があった。まず、やはりというか型の厳密さである。まず、Rubyの場合は戻り値Integerの大きさは言語上制限がないので、呼び出し時に最大ビット幅を指定してチェックしている。一方、わたしの実装はu32を返している。厳密なWebAssemblyの仕様上は扱える整数のビット幅は自由に指定可能なので、Ruby実装の方がそれをうまく表現できている。ただし、同時に仕様書に実際出てくるのは、以下のようにキリのいいビット幅のみということを考えると、個別の型ごとに関数を分けた方がパフォーマンスが出るかもしれない、と考えてこのようにした。

Note: The main integer types occurring in this specification are u32, u64, s32, s64, i8, i16, i32, i64. However, other sizes occur as auxiliary constructions, e.g., in the definition of floating-point numbers.

とはいうものの、ここを何かジェネリックな書き方ができればうれしいな、とも思う。単純にジェネリックにしても、そこにつけるトレイトをどのようにすればいいのか困る。基本数値型をまとめて指定できる方法があればいいのにな、とも少しだけ感じた(不便はほとんどない)。