Rust FFI边界处的panic何时会被认定为未定义行为?
Rust FFI边界的Panic与未定义行为疑问
问题背景
《Rust Nomicon》中有如下示例:
#[no_mangle] extern "C" fn assert_nonzero(input: u32) { assert!(input != 0) }
若调用
assert_nonzero时传入参数0,无论是否以panic=abort编译,运行时都会保证(安全地)终止进程。
而《Rust Reference》则明确指出:
被认定为未定义行为的情况:[...] 使用错误的调用ABI调用函数,或从具有错误unwind ABI的函数中展开。
这两种表述看似矛盾:assert_nonzero采用C ABI,但触发Rust panic时会展开栈。那它到底是保证安全终止进程,还是属于未定义行为(UB)?二者无法并存。
有观点明确指出这属于未定义行为,那么是《Rust Nomicon》已过时,还是存在理解误区?
更核心的问题是:Rust FFI边界处的panic究竟何时会被认定为未定义行为?
解答
1. 《Rust Nomicon》的表述问题
Nomicon中的这段内容存在表述不严谨或过时的问题。当前Rust官方规范(Reference)明确将“从具有错误unwind ABI的函数中展开”列为未定义行为——extern "C"函数的unwind ABI与Rust原生unwind机制不兼容,当这类函数内部触发panic并尝试展开时,就会触发UB。
所谓“安全终止”的表述并不准确:若编译时使用panic=abort,进程会直接终止,不会触发栈展开,这种情况是安全的;但如果使用默认的panic=unwind,栈展开会跨FFI边界,此时进程行为不可预测,无法保证“安全终止”。
2. FFI边界panic触发UB的判定条件
满足以下任一条件时,FFI边界的panic会被认定为未定义行为:
- 被非Rust代码调用的Rust函数(使用非Rust ABI,如
extern "C")内部触发panic,且编译时启用panic=unwind:栈展开会跨越FFI边界,外部代码的调用栈无法处理Rust的unwind机制。 - Rust代码调用外部函数时,外部函数抛出与Rust unwind ABI不兼容的异常,且Rust代码未正确捕获处理:跨ABI的异常/ panic展开会触发UB。
以下情况则是安全的:
- 编译Rust代码时使用
panic=abort:panic发生时直接终止进程,不会触发栈展开,不会跨FFI边界产生问题。 - Rust函数被外部调用时,内部确保不会触发panic,或通过
catch_unwind捕获panic,避免栈展开到FFI边界。
内容的提问来源于stack exchange,提问作者ChrisB
相关产品推荐
相关产品推荐

