编译时通用类型大小检查

lla*_*ram 17 pointers ffi rust

我正在尝试为C集合库(Judy Arrays [1])编写Rust绑定,它只为存储指针宽度值提供了空间.我的公司有相当数量的现有代码,它使用这个空间直接存储非指针值,如指针宽度整数和小结构.我希望我的Rust绑定允许使用泛型类型安全地访问此类集合,但是无法使指针存储语义正常工作.

我有一个std::mem::transmute_copy()用于存储值的基本接口,但该函数显式不执行任何操作以确保源和目标类型的大小相同.我能够通过断言验证集合类型参数在运行时是否具有兼容的大小,但我真的希望检查以某种方式在编译时.

示例代码:

pub struct Example<T> {
    v: usize,
    t: PhantomData<T>,
}

impl<T> Example<T> {
    pub fn new() -> Example<T> {
        assert!(mem::size_of::<usize>() == mem::size_of::<T>());
        Example { v: 0, t: PhantomData }
    }

    pub fn insert(&mut self, val: T) {
        unsafe {
            self.v = mem::transmute_copy(&val);
            mem::forget(val);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

有没有更好的方法来做到这一点,或者这个运行时检查最好的Rust 1.0支持?

(相关问题,解释我为什么不使用mem::transmute().)

[1]我知道现有的rust-judy项目,但它并不支持我想要的指针存储,而且我正在编写这些新的绑定,主要是作为一种学习练习.

Luk*_*odt 5

编译时检查?

有没有更好的方法来做到这一点,或者这个运行时检查是最好的 Rust 1.0 支持吗?

一般来说,有一些 hacky 解决方案可以对任意条件进行某种编译时测试。例如,存在static_assertions板条箱,其提供了一些有用的宏(包括一个宏类似于C ++的static_assert)。但是,这是hacky并且非常有限

在您的特定情况下,我还没有找到在编译时执行检查的方法。这里的根本问题是您不能在泛型类型上使用mem::size_ofmem::transmute。相关问题:#43408#47966。出于这个原因,static_assertions板条箱也不起作用。

如果您考虑一下,这也会导致 Rust 程序员非常不熟悉的一种错误:实例化具有特定类型的泛型函数时的错误。这对于 C++ 程序员来说是众所周知的——Rust 的 trait bound 用于修复那些通常非常糟糕和无用的错误消息。在 Rust 世界中,需要将您的需求指定为 trait bound:类似where size_of::<T> == size_of::<usize>().

然而,这目前是不可能的。曾经有一个相当著名的“依赖于常量的类型系统”RFC,它允许这些类型的边界,但现在被拒绝了。对这些功能的支持正在缓慢但稳步地发展。“Miri” 前段时间被合并到编译器中,允许更强大的常量评估。这是许多事情的促成因素,包括实际合并的“Const Generics”RFC。尚未实施,但预计在2018年或2019年落地。

不幸的是,它仍然无法实现您需要的那种绑定。比较两个 const 表达式的相等性,被故意排除在主要 RFC 之外,以便在未来的 RFC 中解决。

因此可以预期,where size_of::<T> == size_of::<usize>()最终可能会出现类似于 的界限。但这不应该在不久的将来发生!


解决方法

在你的情况下,我可能会引入一个不安全的trait AsBigAsUsize。要实现它,您可以编写一个宏impl_as_big_as_usize来执行大小检查并实现 trait。也许是这样的:

unsafe trait AsBigAsUsize: Sized {
    const _DUMMY: [(); 0];
}

macro_rules! impl_as_big_as_usize {
    ($type:ty) => {
        unsafe impl AsBigAsUsize for $type {
            const _DUMMY: [(); 0] = 
                [(); (mem::size_of::<$type>() == mem::size_of::<usize>()) as usize];
            // We should probably also check the alignment!
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这与使用的技巧基本相同static_assertions。这是有效的,因为我们从不使用size_of泛型类型,而只使用宏调用的具体类型。

所以……这显然远非完美。您的库的用户必须为impl_as_big_as_usize他们想要在您的数据结构中使用的每种类型调用一次。但至少它是安全的:只要程序员只使用宏来实现特征,特征实际上只为与usize. 另外,“AsBigAsUsize不满足特征边界”的错误也是可以理解的。


运行时检查呢?

正如 bluss 在评论中所说,在您的assert!代码中,没有运行时检查,因为优化器对检查进行了常量折叠。让我们用以下代码测试该语句:

#![feature(asm)]

fn main() {
    foo(3u64);
    foo(true);
}

#[inline(never)]
fn foo<T>(t: T) {
    use std::mem::size_of;

    unsafe { asm!("" : : "r"(&t)) }; // black box
    assert!(size_of::<usize>() == size_of::<T>());
    unsafe { asm!("" : : "r"(&t)) }; // black box
}
Run Code Online (Sandbox Code Playgroud)

疯狂的asm!()表达有两个目的:

  • t从 LLVM “隐藏” ,这样 LLVM 就不能执行我们不想要的优化(比如删除整个函数)
  • 在我们将要查看的结果 ASM 代码中标记特定点

使用夜间编译器编译它(在 64 位环境中!):

rustc -O --emit=asm test.rs
Run Code Online (Sandbox Code Playgroud)

像往常一样,生成的汇编代码很难阅读;这里是重要的地方(有一些清理):

unsafe trait AsBigAsUsize: Sized {
    const _DUMMY: [(); 0];
}

macro_rules! impl_as_big_as_usize {
    ($type:ty) => {
        unsafe impl AsBigAsUsize for $type {
            const _DUMMY: [(); 0] = 
                [(); (mem::size_of::<$type>() == mem::size_of::<usize>()) as usize];
            // We should probably also check the alignment!
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

#APP-#NO_APP我们的asm!()表达。

  • foo<bool>情况下:你可以看到,我们的第一个asm!()指令被编译,然后到一个无条件呼叫panic!()由事后之炊(ud2只是说“节目永远达不到这个点,panic!()发散”)。
  • foo<u64>情况下:您可以看到#APP-#NO_APP对(两个asm!()表达式)之间没有任何东西。

所以是的:编译器完全删除了检查

如果编译器拒绝编译代码会更好。但是这样我们至少知道,没有运行时开销。


use*_*342 5

自 Rust 1.57 以来,可以在安全代码中进行编译时检查。截至撰写本文时(Rust 1.67),它们可以使用函数外部的中间编译时常量来实现。操作方法如下:

pub struct Example<T> {
    pub v: usize,
    pub t: PhantomData<T>,
}

impl<T> Example<T> {
    const SIZE_OK: () = assert!(size_of::<T>() == size_of::<usize>());

    pub fn new() -> Example<T> {
        let _ = Self::SIZE_OK;
        Example {
            v: 0,
            t: PhantomData,
        }
    }
}

pub struct Good(usize);
pub struct Bad(u8);

fn main() {
    let _e1 = Example::<Good>::new();  // compiles
    //let _e2 = Example::<Bad>::new(); // doesn't compile
}
Run Code Online (Sandbox Code Playgroud)

操场