您现在的位置是:首页 >技术教程 >研读Rust圣经解析——Rust learn-15(unsafe Rust )网站首页技术教程

研读Rust圣经解析——Rust learn-15(unsafe Rust )

简明编程 2023-06-21 20:00:03
简介研读Rust圣经解析——Rust learn-15(unsafe Rust )

不安全的Rust

Rust 还隐藏有第二种语言,它不会强制执行这类内存安全保证:这被称为 不安全 Rust(unsafe Rust)

不安全 Rust 之所以存在,是因为静态分析本质上是保守的。当编译器尝试确定一段代码是否支持某个保证时,拒绝一些合法的程序比接受无效的程序要好一些。这必然意味着有时代码 可能 是合法的,但如果 Rust 编译器没有足够的信息来确定,它将拒绝该代码。在这种情况下,可以使用不安全代码告诉编译器,“相信我,我知道我在干什么。” 不过千万注意,使用不安全 Rust 风险自担:如果不安全代码出错了,比如解引用空指针,可能会导致不安全的内存使用。

另一个 Rust 存在不安全一面的原因是:底层计算机硬件固有的不安全性。如果 Rust 不允许进行不安全操作,那么有些任务则根本完成不了。Rust 需要能够进行像直接与操作系统交互,甚至于编写你自己的操作系统这样的底层系统编程!这也是 Rust 语言的目标之一。

主要来说共有5大操作

  1. 解引用裸指针
  2. 调用不安全的函数或方法
  3. 访问或修改可变静态变量
  4. 实现不安全 trait
  5. 访问 union 的字段

不过在这里我们只看1,2,3

unsafe关键字

使用unsafe 关键字我们可以创建一块不安全的区域,在其中使用unsafe rust

unsafe{
	//...
}

解引用裸指针

裸指针是可变或不可变的
应用于调用c语言中的接口
裸指针与引用和智能指针的区别在于:

  1. 允许忽略借用规则,可以同时拥有不可变和可变的指针,或多个指向相同位置的可变指针
  2. 不保证指向有效的内存
  3. 允许为空
  4. 不能实现任何自动清理功能

声明一个不可变的裸指针

不可变使用关键字*const并在后部指明类型

let a = 1;
let b = &a as *const i32;

声明可变的裸指针

可变使用关键字*mut并在后部指明类型

let c = &a as *mut i32;

注意点

可以在安全代码中 创建裸指针,只是不能在不安全块之外 解引用裸指针

调用不安全函数或方法

我们通过unsafe关键字可以设置一块区域,区域中使用不安全的代码(函数)

//声明
unsafe fn func(){}
//调用
unsafe{
	func();
}

创建不安全代码的安全抽象

仅仅因为函数包含不安全代码并不意味着整个函数都需要标记为不安全的。事实上,将不安全代码封装进安全函数是一个常见的抽象

以下是官方的例子:

fn main() {
    let mut v = vec![1, 2, 3, 4, 5, 6];

    let r = &mut v[..];

    let (a, b) = r.split_at_mut(3);

    assert_eq!(a, &mut [1, 2, 3]);
    assert_eq!(b, &mut [4, 5, 6]);
}

由于官方对split_at_mut说的非常详细,我再去复制显然没什么必要https://kaisery.github.io/trpl-zh-cn/ch19-01-unsafe-rust.html添加链接描述

使用 extern 函数调用外部代码

我们在学习一些Rust项目的时候,我们很可能在代码中看到一些使用其他语言的例子,事实上这个特性并不是Rust所独有的,只是你在Rust中使用起来更加为所欲为,在其他语言中,很可能是语言帮你隐式的调用,而非你能够感受的到(如:Java获取环境变量)
extern关键字可以创建一个外部函数接口以使用其他语言的函数,以下是调用了C语言中的abs函数,至于成不成功,这需要程序员保证,而不是Rust

extern "C" {
    fn abs(input: i32) -> i32;
}

fn main() {
    unsafe {
        println!("Absolute value of -3 according to C: {}", abs(-3));
    }
}

使用 extern#[no_mangle] 暴露Rust函数给其他语言

相应的,可以调用别的语言的函数,我们也可以暴露自己的函数让其他语言调用

Rust中有一个宏:#[no_mangle]

告诉 Rust 编译器不要 mangle 此函数的名称。Mangling 发生于当编译器将我们指定的函数名修改为不同的名称时,这会增加用于其他编译过程的额外信息,不过会使其名称更难以阅读。每一个编程语言的编译器都会以稍微不同的方式 mangle 函数名,所以为了使 Rust 函数能在其他语言中指定,必须禁用 Rust 编译器的 name mangling。

#[no_mangle]
pub extern "Java" fn get_env(){
	return env::args().collect();
}

访问或修改可变静态变量

数据竞争:当多个线程访问相同的可变全局变量,通常静态变量的名称采用SCREAMING_SNAKE_CASE写法。静态变量只能储存拥有 'static 生命周期的引用,这意味着 Rust 编译器可以自己计算出其生命周期而无需显式标注。访问不可变静态变量是安全的。

常量与不可变静态变量的一个微妙的区别是静态变量中的值有一个固定的内存地址。使用这个值总是会访问相同的地址。另一方面,常量则允许在任何被用到的时候复制其数据。另一个区别在于静态变量可以是可变的。访问和修改可变静态变量都是不安全的

声明静态变量

使用static关键字对静态变量进行声明

static TEST: &str = "TEST";

修改静态变量

由于修改静态变量是非安全的操作,所以我们必须要在unsafe块中

static mut TEST: i32 = 100;

fn add() {
    unsafe {
        TEST += 5
    }
}

fn main() {
    add();
    unsafe {
        println!("{}", TEST);
    }
}

而当多个线程进行访问的时候我们需要用到消息传递机制和共享状态保证数据竞争不会发生

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。