编译时通用类型大小检查 [英] Compile-time generic type size check

查看:85
本文介绍了编译时通用类型大小检查的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试为C集合库(Judy Arrays [1])编写Rust绑定,该库仅为其自身提供了存储指针宽度值的空间.我公司有大量现有代码,可以使用此空间直接存储非指针值,例如指针宽度整数和小结构.我希望Rust绑定允许使用泛型对此类集合进行类型安全的访问,但是在使指针存储语义正确工作方面遇到了麻烦.

I'm attempting to write Rust bindings for a C collection library (Judy Arrays [1]) which only provides itself room to store a pointer-width value. My company has a fair amount of existing code which uses this space to directly store non-pointer values such as pointer-width integers and small structs. I'd like my Rust bindings to allow type-safe access to such collections using generics, but am having trouble getting the pointer-stashing semantics working correctly.

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

I have a basic interface working using std::mem::transmute_copy() to store the value, but that function explicitly does nothing to ensure the source and destination types are the same size. I'm able to verify that collection type parameter is of a compatible size at run-time via an assertion, but I'd really like the check to somehow be at compile-time.

示例代码:

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);
        }
    }
}

是否有更好的方法来执行此操作,或者此运行时检查是否是Rust 1.0最好的支持?

Is there a better way to do this, or is this run-time check the best Rust 1.0 supports?

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

(Related question, explaining why I'm not using mem::transmute().)

[1]我知道现有的rust-judy项目,但它不支持我想要的指针存储,无论如何,我主要是作为学习练习来编写这些新的绑定.

[1] I'm aware of the existing rust-judy project, but it doesn't support the pointer-stashing I want, and I'm writing these new bindings largely as a learning exercise anyway.

推荐答案

编译时检查?

是否有更好的方法来执行此操作,或者此运行时检查是否是Rust 1.0最好的支持?

Is there a better way to do this, or is this run-time check the best Rust 1.0 supports?

通常,有一些骇人听闻的解决方案可以对任意条件进行某种编译时测试.例如,有板条箱,其中提供了一些有用的宏(包括一个类似于C ++的宏) static_assert).但是,这是 hacky 非常有限 .

In general, there are some hacky solutions to do some kind of compile time testing of arbitrary conditions. For example, there is the static_assertions crate which offers some useful macros (including one macro similar to C++'s static_assert). However, this is hacky and very limited.

在您的特定情况下,我还没有找到在编译时执行检查的方法.这里的根本问题是您不能在通用类型上使用mem::size_ofmem::transmute .相关问题:#43408

In your particular situation, I haven't found a way to perform the check at compile time. The root problem here is that you can't use mem::size_of or mem::transmute on a generic type. Related issues: #43408 and #47966. For this reason, the static_assertions crate doesn't work either.

如果考虑到这一点,这还将导致Rust程序员非常不熟悉的一种错误:在实例化具有特定类型的泛型函数时发生的错误.这对于C ++程序员是众所周知的-Rust的特征范围用于修复那些通常非常糟糕和无用的错误消息.在Rust的世界中,需要将您的需求指定为特征绑定:类似where size_of::<T> == size_of::<usize>()的东西.

If you think about it, this would also allow a kind of error very unfamiliar to Rust programmers: an error when instantiating a generic function with a specific type. This is well known to C++ programmers -- Rust's trait bounds are used to fix those often very bad and unhelpful error messages. In the Rust world, one would need to specify your requirement as trait bound: something like where size_of::<T> == size_of::<usize>().

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

However, this is currently not possible. There once was a fairly famous "const-dependent type system" RFC which would allow these kinds of bounds, but got rejected for now. Support for these kinds of features are slowly but steadily progressing. "Miri" was merged into the compiler some time ago, allowing much more powerful constant evaluation. This is an enabler for many things, including the "Const Generics" RFC, which was actually merged. It is not yet implemented, but it is expected to land in 2018 or 2019.

不幸的是,它仍然无法启用所需的绑定类型.比较两个const表达式是否相等,

Unfortunately, it still doesn't enable the kind of bound you need. Comparing two const expressions for equality, was purposefully left out of the main RFC to be resolved in a future RFC.

因此,可以预料的是,类似于where size_of::<T> == size_of::<usize>()的绑定最终将是可能的.但这不应该在不久的将来得到预期!

So it is to be expected that a bound similar to where size_of::<T> == size_of::<usize>() will eventually be possible. But this shouldn't be expected in the near future!

在您的情况下,我可能会介绍一个不安全特征AsBigAsUsize.要实现它,您可以编写一个宏impl_as_big_as_usize,它执行大小检查并实现特征.也许是这样的:

In your situation, I would probably introduce an unsafe trait AsBigAsUsize. To implement it, you could write a macro impl_as_big_as_usize which performs a size check and implements the trait. Maybe something like this:

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!
        }
    }
}

这与static_assertions所使用的技巧基本相同.之所以行之有效,是因为我们从未在泛型类型上使用size_of,而仅在宏调用的具体类型上使用了.

This uses basically the same trickery as static_assertions is using. This works, because we never use size_of on a generic type, but only on concrete types of the macro invocation.

所以...这显然还不完美.库用户必须为要在数据结构中使用的每种类型调用一次impl_as_big_as_usize.但这至少是安全的:只要程序员仅使用宏来隐含该特征,则实际上仅对与usize大小相同的类型实现该特征.此外,错误特质限制AsBigAsUsize不满足"是很容易理解的.

So... this is obviously far from perfect. The user of your library has to invoke impl_as_big_as_usize once for every type they want to use in your data structure. But at least it's safe: as long as programmers only use the macro to impl the trait, the trait is in fact only implemented for types that have the same size as usize. Also, the error "trait bound AsBigAsUsize is not satisfied" is very understandable.

就像评论中所说的那样,在您的assert!代码中,没有运行时检查 ,因为优化程序会不断地折叠检查.让我们用以下代码测试该语句:

As bluss said in the comments, in your assert! code, there is no run-time check, because the optimizer constant-folds the check. Let's test that statement with this code:

#![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
}

疯狂的asm!()表达式有两个用途:

The crazy asm!() expressions serve two purposes:

  • 隐藏"来自LLVM的t,这样LLVM无法执行我们不希望的优化(例如删除整个功能)
  • 在我们将要查看的结果ASM代码中标记特定的地方
  • "hiding" t from LLVM, such that LLVM can't perform optimizations we don't want (like removing the whole function)
  • marking specific spots in the resulting ASM code we'll be looking at

使用夜间编译器对其进行编译(在64位环境中!):

Compile it with a nightly compiler (in a 64 bit environment!):

rustc -O --emit=asm test.rs

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

As usual, the resulting assembly code is hard to read; here are the important spots (with some cleanup):

_ZN4test4main17he67e990f1745b02cE:  # main()
    subq    $40, %rsp
    callq   _ZN4test3foo17hc593d7aa7187abe3E
    callq   _ZN4test3foo17h40b6a7d0419c9482E
    ud2

_ZN4test3foo17h40b6a7d0419c9482E: # foo<bool>()
    subq    $40, %rsp
    movb    $1, 39(%rsp)
    leaq    39(%rsp), %rax
    #APP
    #NO_APP
    callq   _ZN3std9panicking11begin_panic17h0914615a412ba184E
    ud2

_ZN4test3foo17hc593d7aa7187abe3E: # foo<u64>()
    pushq   %rax
    movq    $3, (%rsp)
    leaq    (%rsp), %rax
    #APP
    #NO_APP
    #APP
    #NO_APP
    popq    %rax
    retq

#APP-#NO_APP是我们的asm!()表达式.

  • foo<bool>案例:您可以看到我们的第一个asm!()指令已编译,然后对panic!()进行了无条件调用,之后什么也没发生(ud2只是说程序永远无法到达此位置,panic!()分叉").
  • foo<u64>的情况:您可以看到两个#APP-#NO_APP对(两个asm!()表达式),中间没有任何内容.
  • The foo<bool> case: you can see that our first asm!() instruction is compiled, then an unconditioned call to panic!() is made and afterwards comes nothing (ud2 just says "the program can never reach this spot, panic!() diverges").
  • The foo<u64> case: you can see both #APP-#NO_APP pairs (both asm!() expressions) without anything in between.

是的:编译器完全删除了支票.

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

It would be way better if the compiler would just refuse to compile the code. But this way we at least know, that there's no run-time overhead.

这篇关于编译时通用类型大小检查的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆