You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Rust中实现数组字面量的编译期拼接?

问题描述

我的单元测试里有大量重复的WKB格式字节数组初始化代码,示例如下:

#[rustfmt::skip]
let bytes = [
    0x00, // Byte order
    0x00, 0x00, 0x00, 0x02, // LineString
    0x00, 0x00, 0x00, 0x02, // Number of points
    0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 1.0
    0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 2.0
    0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 2.0
    0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 1.0
];

这是u8、u32和f64等类型按大/小端字节序拼接的WKB格式代码,可读性和可维护性很差。我想优化成更简洁的形式,比如:

/// 1.0f64, big endian.
const ONE_BE: [u8; 8] = [0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
/// 2.0f64, big endian.
const TWO_BE: [u8; 8] = [0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];

let bytes = [
    0x00, // Byte order
    0x00, 0x00, 0x00, 0x02, // LineString
    0x00, 0x00, 0x00, 0x02, // Number of points
    ...ONE_BE, ...TWO_BE,
    ...TWO_BE, ...ONE_BE,
];

但上述代码中的...是我设想的语法,Rust原生并不支持。我试过声明式宏,但它只能展开为单个项,无法生成逗号分隔的元素序列。

需要说明的是:这类常量数量很少,但使用它们的测试用例很多且持续增加,希望测试代码中的样板代码越少越好;这只是测试初始化代码,无需考虑性能。

最佳实现方案

1. 固定长度声明式宏(最简单无依赖)

如果你的常量数组长度固定(比如8字节的f64),可以直接写一个针对固定长度的声明式宏,直接展开数组元素:

macro_rules! expand_8 {
    ($arr:expr) => {
        $arr[0], $arr[1], $arr[2], $arr[3],
        $arr[4], $arr[5], $arr[6], $arr[7]
    };
}

测试代码中直接使用:

/// 1.0f64, big endian.
const ONE_BE: [u8; 8] = [0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
/// 2.0f64, big endian.
const TWO_BE: [u8; 8] = [0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];

#[rustfmt::skip]
let bytes = [
    0x00, // Byte order
    0x00, 0x00, 0x00, 0x02, // LineString
    0x00, 0x00, 0x00, 0x02, // Number of points
    expand_8!(ONE_BE), expand_8!(TWO_BE),
    expand_8!(TWO_BE), expand_8!(ONE_BE),
];

这种方式零依赖,写法简洁,完全适配你的需求场景。

2. 常量函数拼接数组(编译时处理)

如果需要更灵活的数组组合(比如任意长度拼接),可以用Rust的const fn在编译时直接拼接多个数组,生成最终的字节数组:

// 基础的两数组拼接函数
const fn concat_arrays<const N: usize, const M: usize>(a: [u8; N], b: [u8; M]) -> [u8; N + M] {
    let mut result = [0u8; N + M];
    let mut i = 0;
    while i < N {
        result[i] = a[i];
        i += 1;
    }
    let mut j = 0;
    while j < M {
        result[N + j] = b[j];
        j += 1;
    }
    result
}

// 扩展支持4个数组拼接
const fn concat_4_arrays<const A: usize, const B: usize, const C: usize, const D: usize>(
    a: [u8; A], b: [u8; B], c: [u8; C], d: [u8; D],
) -> [u8; A + B + C + D] {
    concat_arrays(concat_arrays(a, b), concat_arrays(c, d))
}

测试代码中组合使用:

const HEADER: [u8; 9] = [
    0x00, // Byte order
    0x00, 0x00, 0x00, 0x02, // LineString
    0x00, 0x00, 0x00, 0x02, // Number of points
];
const ONE_BE: [u8; 8] = [0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
const TWO_BE: [u8; 8] = [0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
const POINTS: [u8; 32] = concat_4_arrays(ONE_BE, TWO_BE, TWO_BE, ONE_BE);

let bytes = concat_arrays(HEADER, POINTS);

所有拼接逻辑都在编译时完成,不会影响运行时性能,也无需额外依赖。

3. 过程宏(最接近设想语法)

如果想要完全模拟你设想的...语法,可以自定义一个过程宏,直接展开任意长度的数组为元素序列:

定义过程宏

在你的crate中启用proc-macro特性,添加syn和quote依赖后,编写宏代码:

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Expr, ExprArray};

#[proc_macro]
pub fn expand(input: TokenStream) -> TokenStream {
    let expr = parse_macro_input!(input as Expr);
    // 解析数组表达式,生成每个元素的引用
    if let Expr::Array(ExprArray { elems, .. }) = expr {
        let elem_refs = elems.iter().enumerate().map(|(i, _)| {
            quote! { #expr[#i] }
        });
        quote! { #(#elem_refs),* }.into()
    } else {
        panic!("expand! expects an array expression")
    }
}

测试中使用

/// 1.0f64, big endian.
const ONE_BE: [u8; 8] = [0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
/// 2.0f64, big endian.
const TWO_BE: [u8; 8] = [0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];

#[rustfmt::skip]
let bytes = [
    0x00, // Byte order
    0x00, 0x00, 0x00, 0x02, // LineString
    0x00, 0x00, 0x00, 0x02, // Number of points
    expand!(ONE_BE), expand!(TWO_BE),
    expand!(TWO_BE), expand!(ONE_BE),
];

这种方式完全匹配你想要的简洁写法,支持任意长度的数组展开。

内容的提问来源于stack exchange,提问作者Thomas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 19:53:11