师悠逸 发表于 4 天前

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

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

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

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

[*]不安全函数内部的代码可以不要再写unsafe了
[*]调用不安全函数的代码块依然需要些unsafe{}代码块
相关书籍建议我们尽量把不安全的操作封装在不安全函数中,起到一个黑盒作用,更容易管理。
不安全特质(trait)
此外,特质也可以是不安全的,例如:
unsafe trait Foo {
    // methods go here
}

unsafe impl Foo for i32 {
    // method implementations go here
}

fn main() {}原书提到一个术语:invariant。原书把它翻译为不变式。
当特质中至少有一个方法包含了编译器无法验证的invariant,特质是不安全,需要把特质标注为unsafe。
这里根据本人的理解,我认为更加合理妥当的如下:
当特质中至少有一个方法包含了编译器无法验证的不变量/不变约束,特质是不安全,需要把特质标注为unsafe。
 
所谓的不变量/不变约束,可以这么理解(来自文心一言):
某个变量永远不会被访问为 null。
某个数据结构始终保持有效状态(如二叉搜索树的有序性)。
指针只指向有效的内存区域。这些有助于我们理解不安全特质。
但我们是不是可以简要理解:不安全特质,就是部分方法使用了不安全代码?
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标准函数、定义和执行一个不安全函数
use std::ffi::CString;
use encoding_rs::GBK;
use libc::{c_char, c_int};
// 在 Rust 中声明 C 的 abs 函数
extern "C" {
    fn abs(x: i32) -> i32;
}
extern "C" {
    // 定义C语言的printf函数
    //#
    fn printf(format: *const c_char, ...)-> c_int;
}

fn main() {
    //1.0 直接调用 C 的 abs 函数,无需任何前置条件
    call_c_abs();

    //1.1 调用C语言函数,需要用到C语言环境
    call_c_function();   // 此处注释掉,因为需要用到C语言环境

    //2.0 不安全函数例子
    //不安全函数例子-被标记为unsafe的函数,可以执行不安全的操作,但必须放在unsafe块中执行
    unsafe {dangerous()};

}

fn call_c_abs(){
    let number = -42;
    let absolute_value = unsafe { abs(number) };
    println!("调用C的abs:{}的绝对值= {}", number, absolute_value);
}

fn call_c_function() {
    // 创建一个C字符串
    let format = CString::new("%s %d\n").unwrap();
   
    // 1. 原始UTF-8字符串
    let utf8_message = "调用C的printf:C语言,我来了!这是发自一个rust的程序的消息";
   
    // 2. 将UTF-8转换为GBK
    let (gbk_bytes, _, _) = GBK.encode(utf8_message);
   
    // 3. 创建一个GBK编码的CString
    // 注意:需要去掉末尾的null字节,因为CString会自动添加
    let gbk_bytes_without_null = if gbk_bytes.ends_with(&) {
      &gbk_bytes[..gbk_bytes.len() - 1]
    } else {
      &gbk_bytes[..]
    };
    let message = CString::new(gbk_bytes_without_null).unwrap();
    let number = 114975;
    // 调用C语言的printf函数
    unsafe {
      //这样打印会乱码,如何不乱码了?
      printf(format.as_ptr(), message.as_ptr(), number);
    }
}

//不安全函数例子      
//至于内容是不是安全的,我们不关心。重点是,这个函数是被标记为unsafe的
unsafe fn dangerous() {
    println!("我是一个不安全的函数,因此不需要unsafe块就可以执行其它不安全的操作。");
    play();
}

unsafe fn play() {
    //注意,本函数基本上和另外一个地方的例子一模一样,但是此处居然不触发致命错误,为什么?
    //如果game的值是英文,那么会有很大概率触发异常,但是如果game的内容是中文,则不会触发致命错误。
    //为什么?
    let game = Box::new(String::from("fuck dog"));//这个有很大概况触发致命错误
    //let game = Box::new(String::from("跑步、捉迷藏、踢足球、下棋"));//这个不会错误,为什么?
    let raw_game = Box::into_raw(game);
    println!("游戏内容: {:?}", *raw_game);
    drop(Box::from_raw(raw_game));
    println!("游戏内容(来自Box指针): {:?}", *raw_game);

执行结果之一:

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

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


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

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: rust学习二十.2、RUST不安全代码之不安全函数、特质和FFI