rust学习二十.2、不安全代码之不安全函数、特质和FFI

打印 上一主题 下一主题

主题 1644|帖子 1644|积分 4932

本文涉及到不安全函数和FFI(foreign function interface)(外部函数接口)
一、简述

在开始前,先介绍下unsafe代码块。
这个其实上一个章节有效到,但是未有正式介绍。
unsafe块形如:
unsafe{
}
unsafe块可以位于一个函数/方法内,也可以位于函数参数中,比方:
println!("r1: {}, r2: {}", unsafe { *r1 }, unsafe { *r2 });1.1、不安全函数和特质

不安全函数
标志为unsafe的函数,只必要在函数前添加unsafe关键字即可,不安全函数的两个要点:

  • 不安全函数内部的代码可以不要再写unsafe了
  • 调用不安全函数的代码块依然必要些unsafe{}代码块
相关书籍建议我们尽量把不安全的操作封装在不安全函数中,起到一个黑盒作用,更容易管理。
不安全特质(trait)
此外,特质也可以是不安全的,比方:
  1. unsafe trait Foo {
  2.     // methods go here
  3. }
  4. unsafe impl Foo for i32 {
  5.     // method implementations go here
  6. }
  7. fn main() {}
复制代码
原书提到一个术语:invariant。原书把它翻译为不变式。
当特质中至少有一个方法包罗了编译器无法验证的invariant,特质是不安全,必要把特质标注为unsafe。
这里根据本人的明白,我认为更加公道妥当的如下:
当特质中至少有一个方法包罗了编译器无法验证的不变量/不变约束,特质是不安全,必要把特质标注为unsafe。
 
所谓的不变量/不变约束,可以这么明白(来自文心一言):
  1. 某个变量永远不会被访问为 null。
  2. 某个数据结构始终保持有效状态(如二叉搜索树的有序性)。
  3. 指针只指向有效的内存区域。
复制代码
这些有助于我们明白不安全特质。
但我们是不是可以简要明白:不安全特质,就是部门方法利用了不安全代码?
1.2、外部函数接口

Foreign Function interface,简写为ffi,中文含义是外部函数接口。
很多编程语言都有外部函数接口,比方java可以通过JNI调用C++的DLL,Python可以利用ctypes调用C++的DLL。
RUST在底层是怎样实现的,不必要太过关心,反正脱离不了一些固定的窠臼,总之就是可以和C语言交互
FFI的存在能过满足以下几个要求:
1.利用现有的一些资源,不至于浪费
2.利用更加擅长的语言编写一些代码,以便提升效率,大概达成特定目的
至于rust怎样对接c,c++,python,java等,不是本文重点,此处略。
二、例子

本例包罗两个主要内容:调用C标准函数、定义和执行一个不安全函数
  1. use std::ffi::CString;
  2. use encoding_rs::GBK;
  3. use libc::{c_char, c_int};
  4. // 在 Rust 中声明 C 的 abs 函数
  5. extern "C" {
  6.     fn abs(x: i32) -> i32;
  7. }
  8. extern "C" {
  9.     // 定义C语言的printf函数
  10.     //#[link(name = "c")]
  11.     fn printf(format: *const c_char, ...)-> c_int;
  12. }
  13. fn main() {
  14.     //1.0 直接调用 C 的 abs 函数,无需任何前置条件
  15.     call_c_abs();
  16.     //1.1 调用C语言函数,需要用到C语言环境
  17.     call_c_function();   // 此处注释掉,因为需要用到C语言环境
  18.     //2.0 不安全函数例子
  19.     //不安全函数例子-被标记为unsafe的函数,可以执行不安全的操作,但必须放在unsafe块中执行
  20.     unsafe {dangerous()};
  21. }
  22. fn call_c_abs(){
  23.     let number = -42;
  24.     let absolute_value = unsafe { abs(number) };
  25.     println!("调用C的abs:{}的绝对值= {}", number, absolute_value);
  26. }
  27. fn call_c_function() {
  28.     // 创建一个C字符串
  29.     let format = CString::new("%s %d\n").unwrap();
  30.    
  31.     // 1. 原始UTF-8字符串
  32.     let utf8_message = "调用C的printf:C语言,我来了!这是发自一个rust的程序的消息";
  33.    
  34.     // 2. 将UTF-8转换为GBK
  35.     let (gbk_bytes, _, _) = GBK.encode(utf8_message);
  36.    
  37.     // 3. 创建一个GBK编码的CString
  38.     // 注意:需要去掉末尾的null字节,因为CString会自动添加
  39.     let gbk_bytes_without_null = if gbk_bytes.ends_with(&[0]) {
  40.         &gbk_bytes[..gbk_bytes.len() - 1]
  41.     } else {
  42.         &gbk_bytes[..]
  43.     };
  44.     let message = CString::new(gbk_bytes_without_null).unwrap();
  45.     let number = 114975;
  46.     // 调用C语言的printf函数
  47.     unsafe {
  48.         //这样打印会乱码,如何不乱码了?
  49.         printf(format.as_ptr(), message.as_ptr(), number);
  50.     }
  51. }
  52. //不安全函数例子        
  53. //至于内容是不是安全的,我们不关心。重点是,这个函数是被标记为unsafe的
  54. unsafe fn dangerous() {
  55.     println!("我是一个不安全的函数,因此不需要unsafe块就可以执行其它不安全的操作。");
  56.     play();
  57. }
  58. unsafe fn play() {
  59.     //注意,本函数基本上和另外一个地方的例子一模一样,但是此处居然不触发致命错误,为什么?
  60.     //  如果game的值是英文,那么会有很大概率触发异常,但是如果game的内容是中文,则不会触发致命错误。
  61.     //  为什么?
  62.     let game = Box::new(String::from("fuck dog"));  //这个有很大概况触发致命错误
  63.     //let game = Box::new(String::from("跑步、捉迷藏、踢足球、下棋"));  //这个不会错误,为什么?
  64.     let raw_game = Box::into_raw(game);
  65.     println!("游戏内容: {:?}", *raw_game);
  66.     drop(Box::from_raw(raw_game));
  67.     println!("游戏内容(来自Box指针): {:?}", *raw_game);
  68. }
复制代码
 
执行结果之一:

 如果把play()方法的game替换为:
 let game = Box::new(String::from("跑步、捉迷藏、踢足球、下棋"));
则有很大概率不触发致命错误:

这个颇让人迷惑,是rust的潜伏bug吗?
三、小结


  • rust的不安全函数,总体是一个好东西,一个方面提供了封装,别的容易定位,最后可以少写unsafe块
  • 外部函数接口(ffi)则使得rust可以和其它语言对接
 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

梦见你的名字

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表