本文涉及到不安全函数和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函数
- //#[link(name = "c")]
- 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(&[0]) {
- &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可以和其它语言对接
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |