如何使用宏生成编译时唯一整数?

How to use a macro to generate compile-time unique integers?

我需要一个程序的几个部分,在不同的模块中,有一个唯一的整数。

例如:

pub fn foo() -> u64 {
    unique_integer!()
}

pub fn bar() -> u64 {
    unique_integer!()
}

(foo() 永远不应 return 与 bar() 相同,但值本身没有意义,不需要在构建过程中保持稳定。[=11= 的所有调用] 必须 return 相同的值,对 bar() 的所有调用也必须如此。最好,但不是必需的,值是连续的。)

有没有办法使用宏来做到这一点?

不,您不能为此使用常规宏。但是,您也许可以找到一个可以提供此功能的程序宏包。

话虽这么说...

不算安全生锈,但是如果我们可以把安全扔掉window那么这应该可以解决问题。

macro_rules! unique_u64 {
    () => {{
        struct PlaceHolder;
        let id = ::std::any::TypeId::of::<PlaceHolder>();
        unsafe { ::std::mem::transmute::<_, u64>(id) }
    }};
}

这可能是未定义的行为,但是因为我们知道每个类型都应该有一个唯一的 TypeId,所以它会产生预期的效果。我知道这是可能的唯一原因是因为我查看了 TypeId 的结构并且知道它包含一个 u64 来区分类型。但是,目前有计划将 TypeIdu64 更改为更稳定且不易出现此类不安全代码的内容。我们无法保证 TypeId 的内容可能会更改为什么,当它确实更改时,如果它仍然具有与 u64.

相同的大小,它可能会默默地失败
或者,

我们可以通过散列 TypeId 在安全生锈中获得类似的结果。现在,它稍微违反了规则,因为我们不能保证它总是会产生唯一的结果。然而,2 个不同的 TypeIds 似乎不太可能散列为相同的值。此外,这保持在安全的 Rust 范围内,并且不太可能在未来的 Rust 版本中被破坏。

macro_rules! unique_u64 {
    () => {{
        use ::std::hash::{Hash, Hasher};
        
        struct PlaceHolder;
        let id = ::std::any::TypeId::of::<PlaceHolder>();
        let mut hasher = ::std::collections::hash_map::DefaultHasher::new();
        id.hash(&mut hasher);
        hasher.finish()
    }};
}

可以用 once_cell 做这样的事情,使用静态原子变量作为计数器:

use core::sync::atomic::{Ordering, AtomicU64};
use once_cell::sync::Lazy;

static COUNTER: AtomicU64 = AtomicU64::new(0);

fn foo() -> u64 {
    static LOCAL_COUNTER: Lazy<u64> = Lazy::new(|| COUNTER.fetch_add(1, Ordering::Relaxed));
    *LOCAL_COUNTER
}

fn bar() -> u64 {
    static LOCAL_COUNTER: Lazy<u64> = Lazy::new(|| COUNTER.fetch_add(1, Ordering::Relaxed));
    *LOCAL_COUNTER
}

fn main() {
    dbg!(foo()); // 0
    dbg!(foo()); // still 0
    dbg!(bar()); // 1
    dbg!(foo()); // unchanged - 0
    dbg!(bar()); // unchanged - 1
}

Playground

而且,是的,重复代码可以像往常一样用宏包装:

macro_rules! unique_integer {
    () => {{
        static LOCAL_COUNTER: Lazy<u64> = Lazy::new(|| COUNTER.fetch_add(1, Ordering::Relaxed));
        *LOCAL_COUNTER
    }}
}

fn foo() -> u64 {
    unique_integer!()
}

fn bar() -> u64 {
    unique_integer!()
}

不完全是一个宏,但无论如何它是一个命题:

#[repr(u64)]
enum Unique {
    Foo,
    Bar,
}

pub fn foo() -> u64 {
    Unique::Foo as u64
}

pub fn bar() -> u64 {
    Unique::Bar as u64
}

如果您不使用变体,编译器应该会警告您。

您可以使用模块路径(包含通向文件的包和模块)、文件名、列和宏调用的行号来计算 compile-time 散列,如下所示:

pub const fn hash(module_path: &'static str, file: &'static str, line: u32, column: u32) -> u64 {
    let mut hash = 0xcbf29ce484222325;
    let prime = 0x00000100000001B3;

    let mut bytes = module_path.as_bytes();
    let mut i = 0;

    while i < bytes.len() {
        hash ^= bytes[i] as u64;
        hash = hash.wrapping_mul(prime);
        i += 1;
    }

    bytes = file.as_bytes();
    i = 0;
    while i < bytes.len() {
        hash ^= bytes[i] as u64;
        hash = hash.wrapping_mul(prime);
        i += 1;
    }

    hash ^= line as u64;
    hash = hash.wrapping_mul(prime);
    hash ^= column as u64;
    hash = hash.wrapping_mul(prime);
    hash
}

macro_rules! unique_number {
    () => {{
        const UNIQ: u64 = crate::hash(module_path!(), file!(), line!(), column!());
        UNIQ
    }};
}

fn foo() -> u64 {
    unique_number!()
}

fn bar() -> u64 {
    unique_number!()
}

fn main() {
    println!("{} {}", foo(), bar()); // 2098219922142993841 2094402417770602149 on the playground
}

(playground)

与可以根据调用顺序 return 不同值的最佳答案相比,这具有一致结果的好处,并且这也完全在编译时计算,从而消除了运行时开销维护一个柜台。

唯一的缺点是您可能会遇到散列值冲突。但机会很小。如果需要,您可以尝试实现一种计算完美哈希值的算法。示例中使用了FNV算法,应该不错,但并不完美。