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项目,但它并不支持我想要的指针存储,而且我正在编写这些新的绑定,主要是作为一种学习练习.
有没有更好的方法来做到这一点,或者这个运行时检查是最好的 Rust 1.0 支持吗?
一般来说,有一些 hacky 解决方案可以对任意条件进行某种编译时测试。例如,存在的static_assertions板条箱,其提供了一些有用的宏(包括一个宏类似于C ++的static_assert)。但是,这是hacky并且非常有限。
在您的特定情况下,我还没有找到在编译时执行检查的方法。这里的根本问题是您不能在泛型类型上使用mem::size_of或mem::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 就不能执行我们不想要的优化(比如删除整个函数)使用夜间编译器编译它(在 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!()表达式)之间没有任何东西。所以是的:编译器完全删除了检查。
如果编译器拒绝编译代码会更好。但是这样我们至少知道,没有运行时开销。
自 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)