【Rust】unsafe rust入门
这篇文章简单介绍下unsafe rust的几个要点
1. 解引用裸指针
裸指针其实就是C++或者说C的指针,与C的指针不同的是,Rust的裸指针还是要分为可变和不可变,*const T
和 *mut T
:
基于引用创建裸指针
let mut num = 5;let r1 = &num as *const i32;let r2 = &mut num as *mut i32;
或者不想用类型转换也可以这么写(书上认为这是一种隐式转换,我觉得就是一种类型声明)
let r3: *const i32 = #let r4: *mut i32 = &mut num;
创建裸指针是安全的行为,而解引用裸指针才是不安全的行为
fn main() {let mut num = 5;let r1 = &num as *const i32;unsafe {println!("r1 is: {}", *r1);}
}
基于内存地址创建裸指针
基于内存地址创建裸指针相当于直接给指针赋值为某个内存地址:
use std::{slice::from_raw_parts, str::from_utf8_unchecked};fn main() {let string = "bluebonnet27";//as_ptr: Converts a string slice to a raw pointer.let pointer_num = string.as_ptr() as usize;let length = string.len();unsafe {//from_raw_parts: Forms a slice from a pointer and a length.//from_utf8_unchecked: Converts a slice of bytes to a string slice without checking that the string contains valid UTF-8let res = from_utf8_unchecked(from_raw_parts(pointer_num as *const u8, length));println!("The {} bytes at 0x{:X} stored: {}",length, pointer_num, res)}
}
我们可以尝试将pointer_num
和length
改成其他值
基于智能指针创建裸指针
还有一种创建裸指针的方式,那就是基于智能指针来创建:
let a: Box<i32> = Box::new(10);
// 需要先解引用a
let b: *const i32 = &*a;
// 使用 into_raw 来创建
let c: *const i32 = Box::into_raw(a);
在C++中也可以通过智能指针创建裸指针,并且这种做法也存在一些问题。比如如下的代码:
auto p = make_shared<int>(42);
int* iPtr = p.get();
{shared_ptr<int>(iPtr);
}int value = *p; // Error! 内存已经被释放
p与iPtr指向了相同的内存,然而通过get方法后,将内存管理权转移给了普通指针。iPtr传递给里面程序块的临时智能指针后,引用计数为1,随后出了作用域,减少为0,释放内存。
2. 调用 unsafe 函数或方法
很简单,加上unsafe的声明就行:
unsafe fn dangerous() {}
fn main() {dangerous();
}
这样是编译不过的,因为dangerous
是个unsafe
函数。加上unsafe
调用即可:
unsafe fn dangerous() {}
fn main() {unsafe {dangerous();}
}
借用官方文档的一句话,“在整个代码库(code base,指构建一个软件系统所使用的全部代码)中,要尽可能减少不安全代码的量”,比如我们上面的这个例子:
fn main() {let string = "bluebonnet27";//as_ptr: Converts a string slice to a raw pointer.let pointer_num = string.as_ptr() as usize;let length = string.len();unsafe {//from_raw_parts: Forms a slice from a pointer and a length.//from_utf8_unchecked: Converts a slice of bytes to a string slice without checking that the string contains valid UTF-8let res = from_utf8_unchecked(from_raw_parts(pointer_num as *const u8, length));println!("The {} bytes at 0x{:X} stored: {}",length, pointer_num, res)}
}
printlin!
是个安全函数,将它放在unsafe唯一的原因是,我们需要在res
的生命周期内打印它。所以我们可以改成这样:
fn get_str(pointer_num: usize, length: usize) -> String {unsafe {//from_raw_parts: Forms a slice from a pointer and a length.//from_utf8_unchecked: Converts a slice of bytes to a string slice without checking that the string contains valid UTF-8String::from(from_utf8_unchecked(from_raw_parts(pointer_num as *const u8,length,)))}
}fn main() {let string = "bluebonnet27";//as_ptr: Converts a string slice to a raw pointer.let pointer_num = string.as_ptr() as usize;let length = string.len();let res = get_str(pointer_num, length);println!("The {} bytes at 0x{:X} stored: {}",length, pointer_num, res)
}
我们将unsafe的部分单独抽成了一个函数。这里的返回值,不想用String
交出所有权,也可以用'static
的&str
或者更简单地,可以直接将res右侧全部用unsafe包裹:
let res = unsafe{ from_utf8_unchecked(from_raw_parts(pointer_num as *const u8, length));}
3. FFI
FFI(Foreign Function Interface)可以用来与其它语言进行交互,将 C/C++ 的代码重构为 Rust 时,先将相关代码引入到 Rust 项目中,然后逐步重构,也是不错的。
当然,除了 FFI 还有一个办法可以解决跨语言调用的问题,那就是将其作为一个独立的服务,然后使用网络调用的方式去访问,HTTP,gRPC 都可以。
言归正传,之前我们提到 unsafe 的另一个重要目的就是对 FFI 提供支持,它的全称是 Foreign Function Interface,顾名思义,通过 FFI , 我们的 Rust 代码可以跟其它语言的外部代码进行交互。
在Rust中调用其他语言的函数
下面的例子演示了如何调用 C 标准库中的 abs
函数(Rust 目前无法直接调用 C++ 库):
extern "C" {fn abs(input: i32) -> i32;
}fn main() {unsafe {println!("Absolute value of -3 according to C: {}", abs(-3));}
}
事实上,不指定 ABI 字符串的默认情况下,外部块会假定使用指定平台上的标准 C ABI 约定来调用当前的库。所以上面的代码这么写也是ok的:
extern {fn abs(input: i32) -> i32;
}
当然大括号不能去掉。在 extern “C” 代码块中,我们列出了想要调用的外部函数的签名。其中 “C” 定义了外部函数所使用的应用二进制接口ABI (Application Binary Interface):ABI 定义了如何在汇编层面来调用该函数。
有三个 ABI 字符串是跨平台的,并且保证所有编译器都支持它们:
extern "Rust"
– 在任何 Rust 语言中编写的普通函数 fn foo() 默认使用的 ABI。extern "C"
– 这等价于 extern fn foo();无论您的 C编译器支持什么默认 ABI。extern "system"
– 在 Win32 平台之外,中通常等价于extern "C"
。在 Win32 平台上,应该使用"stdcall"
,或者其他应该使用的 ABI 字符串来链接它们自身的 Windows API。
4. 访问或修改一个可变的静态变量
静态变量
静态变量允许声明一个全局的变量,常用于全局数据统计,例如我们希望用一个变量来统计程序当前的总请求数
static mut REQUEST_RECV: usize = 0;
fn main() {unsafe {REQUEST_RECV += 1;assert_eq!(REQUEST_RECV, 1);}
}
Rust 要求必须使用unsafe语句块才能访问和修改static变量,因为这种使用方式往往并不安全,其实编译器是对的,当在多线程中同时去修改时,会不可避免的遇到脏数据。
只有在同一线程内或者不在乎数据的准确性时,才应该使用全局静态变量。
和常量相同,定义静态变量的时候必须赋值为在编译期就可以计算出的值(常量表达式/数学表达式),不能是运行时才能计算出的值(如函数)
5. 实现 unsafe 特征
unsafe特征的意义是,特征中存在unsafe的方法,有时候就得需要unsafe的特征:
unsafe trait Foo {// 方法列表
}unsafe impl Foo for i32 {// 实现相应的方法
}fn main() {}
但是在调用 unsafe trait 时,直接直接调用,不需要在 unsafe 块中调用,因为这里的安全已经被实现者保证了,毕竟如果实现者没保证,调用者也做不了什么来保证安全.
Rust 中的 Send
/ Sync
,这两个 trait 都是 unsafe trait,定义如下
pub unsafe auto trait Send {}
pub unsafe auto trait Sync {}
6. 访问 union 中的字段
访问
这个从C中继承而来的数据结构,在Rust中也大多用于和C进行交互,下面就是一个union
的例子:
union MyUnion {f1: u32,f2: f32,
}
union的关键属性是其所有字段共享公共存储。 因此,对union的一个字段的写入可以覆盖其他字段,并且 union的大小由其最大字段的大小决定。
fn main() {//初始化一个union,语法和struct类似let u = MyUnion { f1: 1 };//读取union的值let f = unsafe { u.f1 };println!("u.f1 = {f}");
}
读取值的操作是unsafe的,这也很好理解,编译器并不知道你读取的东西有没有初始化。反正大家都用相同的内存,我说这段数据就是f32
也行,就算它存进去的时候其实是u32
。
let f = unsafe { u.f1 };let tmp = unsafe { u.f2 };println!("u.f1 = {f}");println!("u.f2 = {tmp}");
结果如下:
也可以用模式匹配,当然,这种操作和直接读取没什么区别,所以也必须是unsafe的:
unsafe {match u {MyUnion { f1: 1 } => {println!("one");}MyUnion { f2 } => {println!("{}", f2);}}}
引用
引用操作也是unsafe
的,而且,由于union
各个成员是共享内存的,对一名成员的引用会视为对其他所有成员的引用:
// 错误: 不能同时对 `u` (通过 `u.f2`)拥有多于一次的可变借用
fn test() {let mut u = MyUnion { f1: 1 };unsafe {let b1 = &mut u.f1;
// ---- 首次可变借用发生在这里 (通过 `u.f1`)let b2 = &mut u.f2;
// ^^^^ 二次可变借用发生在这里 (通过 `u.f2`)*b1 = 5;}
// - 首次借用在这里结束assert_eq!(unsafe { u.f1 }, 5);
}
Rust-Analysis也给出了提示:
C++的改进
union存在很多问题,因此C++17设计了一个新的variant
替代原来的union
。
variant的用法如下:
using namespace std;int main()
{variant<int, string, float> myVar;myVar = "Hello variant";
}
union
访问的时候,由于每个成员变量都有自己的变量名,因此直接就可以访问。但是variant
不太行,而且还要更麻烦一点。
最简单的就是用get
cout << get<string>(myVar) << endl;
但是这里存在一个问题,如果类型对了那皆大欢喜;类型错了,还要处理抛出的std::bad_variant_access
异常:
我们可以使用get_if
,先判断类型再进行访问。get_if
判断类型成功会返回指向数据的指针,判断失败会返回空指针。
if(auto ptr = get_if<string>(&myVar))
{cout << *ptr << endl;
}
7. 内联汇编
Rust中的内联汇编
Rust 提供了 asm!
宏,可以让大家在 Rust 代码中嵌入汇编代码,对于一些极致高性能或者底层的场景还是非常有用的,例如操作系统内核开发。
use std::arch::asm;unsafe {asm!("nop");
}
上面代码将插入一个 NOP 指令( 空操作 ) 到编译器生成的汇编代码中,其中指令作为 asm!
的第一个参数传入。
总结
C++中其实没有unsafe这个东西,像类似裸指针这种,在C++中甚至是一种比较常用的用法。毕竟智能指针,比如shared_ptr
,unique_ptr
,用法更为复杂。
所以我个人认为,Rust的unsafe的意义是,将这些不安全的操作变得复杂,变得难写,进而引导程序员选择更加简单,更加好写的安全用法。这和C++如今的处境刚好相反,C++中按照安全原则写出来的代码都比较复杂,这也是历史原因,毕竟不能动现成的代码。
另外,unsafe也是一种承诺,不再由编译器保证代码的安全性,而是由程序员自己来保证。一旦代码出问题,责任全在程序员自己。
相关文章:

【Rust】unsafe rust入门
这篇文章简单介绍下unsafe rust的几个要点 1. 解引用裸指针 裸指针其实就是C或者说C的指针,与C的指针不同的是,Rust的裸指针还是要分为可变和不可变,*const T 和 *mut T: 基于引用创建裸指针 let mut num 5;let r1 &num …...
dpwwn02靶场
靶机下载地址:https://download.vulnhub.com/dpwwn/dpwwn-02.zip 信息收集 ip add 查看kali Linux虚拟机的IP为:10.10.10.128 https://vulnhub.com/entry/dpwwn-2,343/中查看靶机的信息,IP固定为10.10.10.10 所以kali Linux添加仅主机网卡…...
K8S疑难概念理解——Pod,应该以哪种Kind来部署应用,为什么不直接Pod这种kind?
文章目录 一、Pod概念深度理解,为什么一般不直接以kindPod资源类型来部署应用?二、究竟应该以哪种资源类型来部署应用 一、Pod概念深度理解,为什么一般不直接以kindPod资源类型来部署应用? Pod是Kubernetes中的最小部署单元,可以包含一个或…...

LabVIEW进行仪器串行通信与模拟信号采集的比较
在现代测试、测量和控制系统中,设备通常采用两种主要方式与计算机进行交互:一种是通过数字通信接口(如RS-232、RS-485、GPIB等),另一种是通过模拟信号(电压、电流)进行数据输出。每种方式具有其…...
D81【 python 接口自动化学习】- python基础之HTTP
day81 requests请求session用法 学习日期:20241127 学习目标:http定义及实战 -- requests请求session用法 学习笔记: requests请求session用法 import requests# 创建一个会话 reqrequests.session() url "http://sellshop.5istud…...

白鹿 Hands-on:消除冷启动——基于 Amazon Lambda SnapStart 轻松打造 Serverless Web 应用(二)
文章目录 前言一、前文回顾二、在 Lambda 上运行2.1、查看 Amazon SAM template2.2、编译和部署到 Amazon Lambda2.3、功能测试与验证 三、对比 Snapstart 效果四、资源清理五、实验总结总结 前言 在这个环节中,我们将延续《白鹿 Hands-on:消除冷启动——…...

ROC曲线
文章目录 前言一、ROC的应用?二、使用方式1. 数据准备2.绘图可视化 前言 在差异分析中,ROC曲线可以用来评估不同组之间的分类性能差异。差异分析旨在比较不同组之间的特征差异,例如在基因表达研究中比较不同基因在不同条件或组织中的表达水平…...
c++ 位图和布隆过滤器
位图(bitmap) 定义 位图是一种使用位数组存储数据的结构。每一位表示一个状态,通常用于快速判断某个值是否存在,或者用来表示布尔类型的集合。 特点 节省空间:一个字节可以表示8个状态。高效操作:位操作…...
阿里云CPU过载的一点思考
现象:阿里云ECS服务器连续5个周期CPU超90%告警 分析: max_connections和max_user_connections都做了限制,但是依然告警,服务器上有四个子服务,查看了每个服务的配置文件,发现使用同一个数据库账号&#x…...

单片机学习笔记 15. 串口通信(理论)
更多单片机学习笔记:单片机学习笔记 1. 点亮一个LED灯单片机学习笔记 2. LED灯闪烁单片机学习笔记 3. LED灯流水灯单片机学习笔记 4. 蜂鸣器滴~滴~滴~单片机学习笔记 5. 数码管静态显示单片机学习笔记 6. 数码管动态显示单片机学习笔记 7. 独立键盘单片机学习笔记 8…...
算法训练营day22(二叉树08:二叉搜索树的最近公共祖先,插入,删除)
第六章 二叉树part08 今日内容: ● 235. 二叉搜索树的最近公共祖先 ● 701.二叉搜索树中的插入操作 ● 450.删除二叉搜索树中的节点 详细布置 235. 二叉搜索树的最近公共祖先 相对于 二叉树的最近公共祖先 本题就简单一些了,因为 可以利用二叉搜索树的…...
Linux history 命令详解
简介 history 命令显示当前 shell 会话中以前执行过的命令列表。这对于无需重新输入命令即可重新调用或重新执行命令特别有用。 示例用法 显示命令历史列表 history# 示例输出如下:1 ls -l 2 cd /var/log 3 cat syslog执行历史记录中的命令 !<number>…...

Kafka知识体系
一、认识Kafka 1. kafka适用场景 消息系统:kafka不仅具备传统的系统解耦、流量削峰、缓冲、异步通信、可扩展性、可恢复性等功能,还有其他消息系统难以实现的消息顺序消费及消息回溯功能。 存储系统:kafka把消息持久化到磁盘上,…...

【Android】EventBus的使用及源码分析
文章目录 介绍优点基本用法线程模式POSTINGMAINMAIN_ORDEREDBACKGROUNDASYNC 黏性事件 源码注册getDefault()registerfindSubscriberMethods小结 postpostStickyunregister 介绍 优点 简化组件之间的通信 解耦事件发送者和接收者在 Activity、Fragment 和后台线程中表现良好避…...

【大数据学习 | Spark调优篇】Spark之内存调优
1. 内存的花费 1)每个Java对象,都有一个对象头,会占用16个字节,主要是包括了一些对象的元信息,比如指向它的类的指针。如果一个对象本身很小,比如就包括了一个int类型的field,那么它的对象头实…...

Linux:文件系统inode
早期,存储文件的设备是磁盘(当下的市场几乎都是SSD),但大家习惯的把它们都称为磁盘,磁盘是用来表示区分内存的存储设备。而在操作系统看来,这个存储设备的结构就是一个线性结构,这一点很重要。 …...
力扣难题解析
滑动窗口问题 76.最小覆盖子串 题目链接:76. 最小覆盖子串 - 力扣(LeetCode) 题目描述: 给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空…...
4.5-Channel 和 Flow:SharedFlow 和 StateFlow
文章目录 SharedFlow数据流的收集和事件订阅的区别launchIn() 和 shareIn() 的区别SharedFlow 与 Flow、Channel 的区别shareIn() 适用场景 shareIn() 的具体参数说明shareIn() 的 replay 参数shareIn() 的 started 参数WhileSubscribed() 的参数及适用场景 MutableSharedFlow、…...
Qt | TCP服务器实现QTcpServer,使用线程管理客户端套接字
点击上方"蓝字"关注我们 01、QTcpServer >>> QTcpServer 是 Qt 网络模块中的一个类,用于实现TCP服务器。它允许创建一个服务器,可以接受来自客户端的连接。QTcpServer 是事件驱动的,这意味着它将通过信号和槽机制处理网络事件。 常用函数 构造函数: QT…...

【提高篇】3.6 GPIO(六,寄存器介绍,下)
目录 2.3 输出速度寄存器OSPEEDR(GPIOx_OSPEEDR) (x = A..I) 2.4 上拉/下拉寄存器 (GPIOx_PUPDR) (x = A..I) 2.5 输入数据寄存器(IDR) 2.6 输出数据寄存器(ODR) 2.7 置位/复位寄存器(BSRR) 2.8 BSRR与ODR寄存器的区别 2.3 输出速度寄存器OSPEEDR(GPIOx_OSPEEDR) (…...
Nginx server_name 配置说明
Nginx 是一个高性能的反向代理和负载均衡服务器,其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机(Virtual Host)。 1. 简介 Nginx 使用 server_name 指令来确定…...

Java-41 深入浅出 Spring - 声明式事务的支持 事务配置 XML模式 XML+注解模式
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...

全志A40i android7.1 调试信息打印串口由uart0改为uart3
一,概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本:2014.07; Kernel版本:Linux-3.10; 二,Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01),并让boo…...
使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度
文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...

深入浅出深度学习基础:从感知机到全连接神经网络的核心原理与应用
文章目录 前言一、感知机 (Perceptron)1.1 基础介绍1.1.1 感知机是什么?1.1.2 感知机的工作原理 1.2 感知机的简单应用:基本逻辑门1.2.1 逻辑与 (Logic AND)1.2.2 逻辑或 (Logic OR)1.2.3 逻辑与非 (Logic NAND) 1.3 感知机的实现1.3.1 简单实现 (基于阈…...
Webpack性能优化:构建速度与体积优化策略
一、构建速度优化 1、升级Webpack和Node.js 优化效果:Webpack 4比Webpack 3构建时间降低60%-98%。原因: V8引擎优化(for of替代forEach、Map/Set替代Object)。默认使用更快的md4哈希算法。AST直接从Loa…...
BLEU评分:机器翻译质量评估的黄金标准
BLEU评分:机器翻译质量评估的黄金标准 1. 引言 在自然语言处理(NLP)领域,衡量一个机器翻译模型的性能至关重要。BLEU (Bilingual Evaluation Understudy) 作为一种自动化评估指标,自2002年由IBM的Kishore Papineni等人提出以来,…...
Python 训练营打卡 Day 47
注意力热力图可视化 在day 46代码的基础上,对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...

Mac flutter环境搭建
一、下载flutter sdk 制作 Android 应用 | Flutter 中文文档 - Flutter 中文开发者网站 - Flutter 1、查看mac电脑处理器选择sdk 2、解压 unzip ~/Downloads/flutter_macos_arm64_3.32.2-stable.zip \ -d ~/development/ 3、添加环境变量 命令行打开配置环境变量文件 ope…...