我正在尝试为C集合库(Judy Arrays [1])编写Rust绑定,它只为存储指针宽度值提供了空间 . 我的公司有相当数量的现有代码,它使用这个空间直接存储非指针值,如指针宽度整数和小结构 . 我希望我的Rust绑定允许使用泛型类型安全地访问此类集合,但是无法使指针存储语义正常工作 .
我有一个使用 std::mem::transmute_copy()
来存储值的基本接口,但该函数显式不执行任何操作以确保源和目标类型的大小相同 . 我'm able to verify that collection type parameter is of a compatible size at run-time via an assertion, but I' d非常喜欢检查以某种方式在编译时 .
示例代码:
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支持?
(Related question,解释了我为什么不使用 mem::transmute()
. )
[1]我知道现有的rust-judy项目,但它并不支持我想要的指针存储,而且我正在编写这些新的绑定,主要是作为一种学习练习 .
2 回答
编译时检查?
一般来说, there are some hacky solutions 要做一些任意条件的编译时间测试 . 例如,有the static_assertions crate提供了一些有用的宏(包括一个类似于C的
static_assert
的宏) . 但是,这是hacky和 very limited .在您的特定情况下,我还没有找到在编译时执行检查的方法 . 这里的根本问题是 you can't use mem::size_of or mem::transmute on a generic type . 相关问题:#43408和#47966 . 出于这个原因,
static_assertions
箱也不起作用 .如果你考虑一下,这也会给Rust程序员带来一些非常不熟悉的错误:在实例化具有特定类型的泛型函数时出错 . 这是C程序员所熟知的 - Rust的特征界限用于修复那些通常非常糟糕且无用的错误消息 . 在Rust世界中,人们需要将您的需求指定为特征限制:类似
where size_of::<T> == size_of::<usize>()
.但是,目前这是不可能的 . 曾经有一个相当着名的"const-dependent type system" RFC允许这种类型的界限,但现在被拒绝了 . 对这些功能的支持正在缓慢但稳步地发展 . 前一段时间"Miri"已合并到编译器中,允许更强大的常量评估 . 这是许多事情的推动者,包括实际合并的the "Const Generics" RFC . 它尚未实施,但预计将于2018年或2019年落地 .
不幸的是,它仍然无法实现您需要的那种绑定 . 比较两个const表达式的相等性,was purposefully left out of the main RFC将在未来的RFC中解析 .
因此可以预期,类似于
where size_of::<T> == size_of::<usize>()
的界限最终是可能的 . 但这不应该在不久的将来预期!解决方法
在你的情况下,我可能会引入一个不安全的特征
AsBigAsUsize
. 要实现它,您可以编写宏impl_as_big_as_usize
,它执行大小检查并实现特征 . 也许是这样的:这与
static_assertions
使用的技巧基本相同 . 这是有效的,因为我们从不在泛型类型上使用size_of
,而只在宏调用的具体类型上使用size_of
.所以......这显然远非完美 . 对于要在数据结构中使用的每种类型,库的用户必须调用一次
impl_as_big_as_usize
. 但至少它是安全的:只要程序员只使用宏来实现特征,该特征实际上只适用于与usize
具有相同大小的类型 . 此外,错误“特质绑定AsBigAsUsize
不满意”是非常容易理解的 .运行时检查怎么样?
正如bluss在评论中所说,在你的
assert!
代码中,有 no run-time check ,因为优化器常量 - 折叠检查 . 让我们用这段代码测试那个语句:疯狂的
asm!()
表达式有两个目的:“隐藏”
t
来自LLVM,这样LLVM可以't perform optimizations we don' t想要(比如删除整个函数)标记我们将要查看的生成的ASM代码中的特定位置
用夜间编译器编译它(在64位环境中!):
像往常一样,生成的汇编代码很难阅读;这里是重要的点(有一些清理):
#APP
-#NO_APP
对是我们的asm!()
表达 .foo<bool>
案例:您可以看到我们的第一个asm!()
指令被编译,然后对panic!()
进行无条件调用,之后什么都没有(ud2
只是说“程序永远不会到达这个位置,panic!()
发散”) .foo<u64>
案例:您可以看到#APP
-#NO_APP
对(两个asm!()
表达式),两者之间没有任何内容 .所以是的:编译器 removes the check completely .
如果编译器只是拒绝编译代码会更好 . 但是这种方式我们至少知道,没有运行时开销 .
与接受的答案相反,您可以在编译时检查!
诀窍是在使用优化进行编译时插入对死代码路径中未定义的C函数的调用 . 如果断言失败,您将收到链接器错误 .