c - 如何将 Rust 中的非可变引用传递给不使用 const 的 C-API(即使它应该使用)?
问题描述
我有一个 C-API 的包装器:
#[repr(transparent)]
pub struct Request(http_request_t);
这个包装器提供了几种与请求交互的方法:
impl Request {
pub fn bytes_received(&self) -> usize {
unsafe {
http_request_bytes_received(&self.0 as *const http_request_t)
}
}
}
不幸的是,C-API 对const
-correctness没有那么严格,因此有一个类型签名,usize http_request_bytes_received(*http_request_t)
其由bindgen
to忠实地转换http_request_bytes_received(*mut http_request_t) -> usize
。
现在我可以摆脱这种情况了,但是从&T
to转换*mut T
很容易导致未定义的行为(这是一个令人讨厌的转换)。但它可能没问题,因为http_request_bytes_received
doesn't mutate http_request_t
。
一种可能的替代方法是使用UnsafeCell
so http_request_t
is internal mutable:
#[repr(transparent)]
pub struct Request(UnsafeCell<http_request_t>);
impl Request {
pub fn bytes_received(&self) -> usize {
unsafe {
http_request_bytes_received(self.0.get())
}
}
}
这种方法是否合理,会有任何严重的缺点吗?
(我想它可能会限制一些 Rust 优化并且会做出Request
!Sync
)
解决方案
简短的回答:只需将其转换为*mut T
并将其传递给 C。
长答案:
最好先了解为什么转换*const T
为*mut T
容易出现未定义的行为。
Rust 的内存模型确保 a&mut T
不会与其他任何东西产生别名,因此编译器可以自由地,例如,完全破坏 T 然后恢复其内容,而程序员无法观察到这种行为。如果 a &mut T
and&T
共存并指向同一位置,则会出现未定义的行为,因为如果您从&T
while 编译器 clobbers读取会发生什么&mut T
?类似地,如果你有&T
,编译器假定没有人会修改它(不包括通过 的内部可变性UnsafeCell
),如果它指向的内存被修改,则会出现未定义的行为。
有了背景,很容易看出为什么*const T
to*mut T
是危险的——你不能取消引用结果指针。如果你曾经取消引用*mut T
,你已经获得了&mut T
,它会是 UB。但是,转换操作本身是安全的,您可以安全地将其*mut T
转换为*const T
并取消引用它。
这是 Rust 语义;在C端,关于的保证T*
很弱。如果您持有 a T*
,编译器不能假定没有共享者。事实上,编译器甚至不能断言它指向有效地址(它可能是空指针或结束指针)。除非代码显式写入指针,否则 C 编译器无法生成存储指令到内存位置。
C 端中较弱的含义T*
意味着它不会违反 Rust 关于&T
. 您可以安全地&T
转换为*mut T
C 并将其传递给 C,前提是 C 端从不修改指针指向的内存。
T * restrict
请注意,您可以指示 C编译器该指针不会与任何其他const
使用restrict
.
推荐阅读
- javascript - 使谷歌图表响应,同时保持父元素的纵横比不变
- python - 建议机器人 discord.py
- powershell - Powershell 内置命令停止工作
- python-3.x - 查找所有坐标点之间的成对距离,用共线点过滤距离
- ios - 表格中的垂直线未在暗模式下显示(快速)
- python - Django-Haystack 没有找到任何字段
- function - math.Max 在哪里定义?
- php - php date_diff 函数返回 0
- jenkins - GitSCM 结帐步骤中的分支列表?
- python - mypy:“__add__”的签名与超类型“元组”不兼容 - 但 __sub__ 一切正常