这节我们主要关注 Rust 导出共享库时的错误处理。主要涉及到:
  • Option 和 Result 的处理
  • panic 的处理

错误对于软件来说是不可避免的,错误处理是保证程序健壮性的前提,编程语言一般都会有一些机制来处理出现错误的情况,大致分为两种:抛出异常和作为值返回。Rust 中没有异常,而是将错误作为值返回,并且通过将错误分成两个主要类别可恢复错误(Result<T, E>)和不可恢复错误(panic!)提供了 Rust 特色的错误处理机制。C 虽然错误处理机制简陋,但最常见也是将错误作为值返回,其中的 POSIX 风格就是函数返回一个int值,其中0表示成功,而负值表示错误。基于setjmp/longjmp的错误处理不属于此节的讨论范畴,如果有必要后面再做说明。

Option 和 Result 的处理

在 FFI 中允许使用任何T: SizedOption<&T>Option<&mut T>,代替显式地进行无效性(nullity )检查的指针。这是由于 Rust 保证了可空指针优化(nullable pointer optimization),在 C 端可以接受可空指针。C 端的NULL在 Rust 中被转换为None,而非空指针被封装在Some中。我们知道 Rust 中的Result <T,E>是用于返回和传播错误的类型,其实质是一个枚举,其中Ok(T)表示成功并包含一个值,而Err(E)表示错误并包含一个错误值。在设计 Rust 导出共享库时,我们可以使用返回值的错误处理机制,使 C 调用者可以通过检查返回值来检测何时发生了错误,并获得相关的错误信息。对于 Option 和 Result 的转换,我们一般采取以下一些方法:

  • 简单的返回 C 中常用的数值,0 表示正确,-1 表示错误。
  • 返回类似于 C 中的全局 errno,创建一个线程局部变量(thread_local!),并在每次收到Option参数后进行检查,返回相应的错误信息。
  • 我们可以使用原始指针std::ptr::nullstd::ptr::null_mut来创建表示 C 端的空指针。

本节我们采取简单的返回数值,示例如下:

#[no_mangle]
pub unsafe extern "C" fn handle_option(x: c_float, y: c_float) -> i32 {
   // The return value of the function is an option
   let result = divide(x, y);

   // Pattern match to retrieve the value
   match result {
       // The division was valid
       Some(_) => 0,
       // The division was invalid
       None    => -1,
   }
}

#[no_mangle]
pub unsafe extern "C" fn handle_result(s: *const c_char) -> i32 {
   if (s as *mut c_void).is_null() {
       return -1;
   }

   let vb = CStr::from_ptr(s).to_str().unwrap();
   let version = parse_version(vb);
   match version {
       Ok(_) => 0,
       Err(_) => -1,
   }
}

panic 的处理

同时跨越 FFI 边界的panic会导致未定义的行为(Undefined Behavior,UB),我们还需要确保我们的 FFI 绑定是异常安全(Exception Safety)的。也就是说如果 Rust 导出库的代码可能会出现panic,则需要有个处理机制。在 FFI 绑定时我们可以使用catch_unwind将其包含在 Rust 中,从而不跨越 FFI 边界。

use std::panic::catch_unwind;

fn may_panic() {
   if rand::random() {
       panic!("panic happens");
   }
}

#[no_mangle]
pub unsafe extern "C" fn no_panic() -> i32 {
   let result = catch_unwind(may_panic);
   match result {
       Ok(_) => 0,
       Err(_) => -1,
   }
}

请注意,catch_unwind只能捕获 Rust 中的展开(unwindingpanic,而不能处理 Rust 中的终止程序(abortpanic

当出现 panic 时,Rust 程序默认会开始展开,这意味着 Rust 会回溯栈并清理它遇到的每一个函数的数据,不过这个回溯并清理的过程有很多工作。另一种选择是直接终止,这会不清理数据就退出程序。那么程序所使用的内存需要由操作系统来清理。通过在 Cargo.toml 的 [profile] 部分增加 panic = 'abort',程序在panic时会由展开切换为终止。


©著作权归作者所有:来自51CTO博客作者mob604756f79c64的原创作品,如需转载,请注明出处,否则将追究法律责任

更多相关文章

  1. 数组的学习
  2. 210427 PHP 运算符 流程控制 字符串定义 定界符 require/include
  3. 【Rust日报】2020-09-19 Rust 2021: 降低门槛
  4. 【Rust日报】2020-09-21 Rust宣布成立错误处理项目组
  5. #PHP函数的返回值,参数,匿名函数
  6. 总结函数的返回值,参数 2. 实例演绎你对课上回调函数,匿名函数的
  7. 4月30日做作业卷子
  8. 函数的返回值,参数
  9. PHP:回调函数/递归函数/数组函数/二维数组里的键值name换成user

随机推荐

  1. android复制数据库到SD卡(网上搜集,未经验
  2. Android中通过Intent 调用图片、视频、音
  3. [Android]PhoneGap源码分析——CallbackS
  4. Android(安卓)异步获取网络图片并处理图
  5. Android四大基本组件介绍与生命周期
  6. android 横屏重启的解决方案
  7. Android 强制设置横屏或竖屏 设置全屏
  8. android之ListView和SimpleAdapter的组合
  9. android各种提示Dialog 弹出框
  10. 系出名门Android(7) - 控件(View)