簡體   English   中英

Rust:數組長度的宏

[英]Rust: macro for the length of an array

我有一個包含原始二進制數據的文件,我想將其加載到 4 字節長u32字的數組中。

可以通過在編譯時包含文件內容來實現:

let bytes = include_bytes!("audio.raw");

然后,我可以將其轉換為數組u32使用的話transmute 新數組的長度顯然應該是原始字節數組的 1/4。

let audio = unsafe { std::mem::transmute::<[u8; 63504], [u32; 15876]>(*bytes) };
// works

正如您在上面看到的,我必須對輸入和輸出數組的長度進行硬編碼。 但是,當試圖避免對這些數字進行硬編碼時,它不起作用:

let audio = unsafe { std::mem::transmute::<[u8; bytes.len()], [u32; bytes.len()/4]>(*bytes) };
// error: non-constant value

.len()似乎是在運行時調用的,並且由於無法在 Rust 中分配動態數組,因此會產生錯誤。 但是理論上,應該有一種方法可以在編譯階段計算所需的長度,因為bytes數組的長度是固定的。 所以我的問題是:是否有一個宏可以返回靜態數組的長度?

(我非常清楚向量可以進行動態分配,我的問題明確與固定大小的數組有關。)

示例代碼include_bytes替換為硬編碼數組):

fn main() {
    // let bytes = include_bytes!("audio.raw");
    let bytes = [1, 0, 0, 0, 2, 0, 0, 0];

    // works:
    let audio = unsafe { std::mem::transmute::<[u8; 8], [u32; 2]>(bytes) };

    // error:
    let audio = unsafe { std::mem::transmute::<[u8; bytes.len()], [u32; bytes.len() / 4]>(bytes) };

    println!("{}", audio[1]);
}

array實現了Deref<[T]> (slice) 並且它的len方法不是常量。

要獲得表示數組長度的常量值,特征助手可能適合您的需要。

穩定

trait ArrayLen {
    const SIZE: usize;
}

macro_rules! impl_array(
    ($($size:expr),+) => {
        $(
            impl<'a, T> ArrayLen for &'a [T; $size] {
                const SIZE: usize = $size;
            }
        )+
    }
);

impl_array!(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 20, 24, 32, 36,
            0x40, 0x80, 0x100, 0x200, 0x400, 0x800, 0x1000, 0x2000, 0x4000, 0x8000,
            0x10000, 0x20000, 0x40000, 0x80000, 0x100000);

fn print<T>(_: T)
where
    T: ArrayLen
{
    println!("{}", T::SIZE);
}

fn main() {
    let bytes = include_bytes!("four_bytes_file.something");
    // The length of `bytes` must match one of the implementations
    print(bytes);
}

每晚

#![feature(const_generics)]

trait ArrayLen {
    const SIZE: usize;
}

impl<'a, T, const N: usize> ArrayLen for &'a [T; N] {
    const SIZE: usize = N;
}

fn print<T>(_: T)
where
    T: ArrayLen
{
    println!("{}", T::SIZE);
}

fn main() {
    let bytes = include_bytes!("any_file.something");
    print(bytes);
}

盡管如此, [T; CONST_PARAMETER / 4] [T; CONST_PARAMETER / 4]目前是不可能的,但事情可能會隨着https://github.com/rust-lang/rust/issues/60471而改變。

一般情況下,這是不可能變換的陣列u8到陣列u32而不復制。

此轉換僅在以下條件下有效:

  1. 結盟。 數組需要對齊以容納u32 ,這通常意味着它需要從四的倍數的地址開始。 對於使用include_bytes!()宏創建的字節數組,不能保證合適的對齊。 這可以使用此代碼( playgorund

     let bytes: [u8; 5] = [1, 2, 3, 4, 5]; dbg!(bytes.as_ptr().align_offset(std::mem::align_of::<u32>()));

    當我嘗試在操場上運行它時,結果為 1,這意味着字節數組未對齊以容納u32 ,但無法保證結果。

  2. 字節序。 數組中的字節必須以與目標架構字節序匹配的字節序表示 32 位整數。 如果您從文件中加載的數據是小端的,任何將數據解釋為 32 位整數而不進行任何轉換的代碼都只能在小端平台上運行。

  3. 長度。 字節數組的長度需要是u32大小的倍數,即 4 的倍數。

如果滿足這些條件,理論上您可以使用以下代碼將字節數組轉換為u32的切片:

let audio = unsafe {
    let ptr = bytes.as_ptr() as *const u32;
    let factor = std::mem::size_of::<u32>() / std::mem::size_of::<u8>();
    let len = bytes.len() / factor;
    if ptr.align_offset(std::mem::align_of::<u32>()) == 0
        && bytes.len() % factor == 0
        && cfg!(target_endian = "little")
    {
        std::slice::from_raw_parts(ptr, len)
    } else {
        // Perform proper conversion.
    }
};

然而,這似乎不值得麻煩——無論如何您都需要實現真正的轉換代碼。

我的建議是使用實際的音頻文件格式,並使用庫加載它——這將為您節省各種麻煩。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM