26.高级特性(上)

目录

  • 一、不安全的Rust
  • 二、不安全的超能力
    • 2.1 概念
    • 2.2 解引用裸指针
    • 2.3 调用不安全的函数或方法
    • 2.3 创建不安全代码的安全抽象
    • 2.4 使用extern函数调用外部代码
    • 2.5 访问或修改可变静态变量
    • 2.6 实现不安全trait
    • 2.7 访问联合体中的字段
  • 三、高级trait
    • 3.1 关联类型在trait定义中指定占位符类型
    • 3.2 关联类型与泛型的区别
    • 3.3 默认泛型类型参数和运算符重载
    • 3.4 完全限定语言与消歧义:如何调用相同名称的方法
    • 3.5 使用supertrait来要求trait附带其它trait的功能
    • 3.6 使用newtype模式用于在外部类型上实现外部trait

一、不安全的Rust

  • Rust隐藏有第二种语言,它没有强制内存安全保证,这被称为不安全Rust(unsafe Rust);
  • 不安全Rust存在的原因:
    • 静态分析是保守的,使用unsafe Rust则是告诉编译器自己知道在做啥;
    • 计算机硬件本身是不安全的, Rust需要能够进行底层系统编程;

二、不安全的超能力

2.1 概念

  • 可以通过unsafe关键字将Rust切换到不安全Rust,开启一个块,存放不安全代码;
  • Unsafe Rust里执行的五种操作
    1. 解引用裸指针;
    2. 调用不安全的函数或方法;
    3. 访问或修改可变静态变量;
    4. 实现不安全trait;
    5. 访问union的字段;
  • unsafe 并不会关闭借用检查器或禁用任何其他安全检查;
  • unsafe 关键字只是提供了那五个不会被编译器检查内存安全的功能;
  • 隔离unsafe代码,最好将它们封装进一个安全的抽象并提供安全API;

2.2 解引用裸指针

  • 可变的原始指针: *mut T;
  • 不可变的原始指针: *const T;
  • 不可变意味着指针解引用之后不能直接赋值;
  • *不是解引用运算符,它是类型名称的一部分;
  • 裸指针与引用和智能指针的区别:
    • 允许忽略借用规则,可以同时拥有不可变和可变的指针,或多个指向相同位置的可变指针;
    • 不保证指向有效的内存;
    • 允许为空;
    • 不能实现任何自动清理功能;
fn main() {let mut num = 5;let r1 = &num as *const i32;let r2 = &mut num as *mut i32;let address = 0x012345usize;let r = address as *const i32;
//    println!("r1 is: {}", *r1);
//    println!("r2 is: {}", *r2);
}
  • 代码同时创建不可变和可变裸指针;
  • 可以在安全代码中创建裸指针,但是解引用必须在unsafe代码块里;
  • 使用as将不可变和可变引用强制转换为对应的裸指针类型;
  • address是一个不能确定其有效性的裸指针;
  • 放开最后两行注释而直接进行解引用会产生错误;

在这里插入图片描述

  • 使用unsafe {}把他们包裹起来就能正确运行了;
  • 为什么要用原始指针?
    • 与C语言进行交互;
    • 构建借用检查器无法理解的安全抽象;

2.3 调用不安全的函数或方法

  • unsafe函数或方法:在定义前加上unsafe关键字;
  • 调用unsafe函数要先满足条件(看文档),Rust无法对这些条件进行验证;
  • 需要在unsafe块里调用;
unsafe fn dangerous() {}fn main() {dangerous();
}
  • 编译错误
  • 在main函数里,用unsafe{} 将dangerous函数调用包裹起来就能通过了;

在这里插入图片描述

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

  • 函数包含不安全代码并不意味着整个函数都需要标记为不安全;
  • 将不安全代码封装进安全函数是一个常见的抽象;
fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {let len = slice.len();assert!(mid <= len);(&mut slice[..mid],&mut slice[mid..])
}fn main() {let mut v = vec![1, 2, 3, 4, 5, 6];let (a, b) = split_at_mut(&mut v[..], 3);println!("a = {:?}", a);println!("b = {:?}", b);
}
  • 上述代码中split_at_mut函数将传入的可变切片进行分割,返回两个可变切片;
  • 编译报错

在这里插入图片描述

  • Rust的借用检查器任何代码将同一个变量借用了两次;
  • 代码里明确了两个切片不会重叠,这就要触及不安全代码了;
  • 这就需要用unsafe块,裸指针和一些不安全函数调用来修改split_at_mut
use std::slice;
fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {let len = slice.len();let ptr = slice.as_mut_ptr();assert!(mid <= len);unsafe {(slice::from_raw_parts_mut(ptr, mid),slice::from_raw_parts_mut(ptr.add(mid), len - mid))}
}
  • 使用len方法获取切片的长度,使用as_mut_ptr获取访问切片的裸指针;
    • slice变量是一个i32类型的可变切片,因此as_mut_ptr返回一个*mut i32类型的裸指针,储存在 ptr 变量中;
  • slice::from_raw_parts_mut函数获取一个裸指针和一个长度来创建一个切片;
  • 无需将split_at_mut函数标记为unsafe,且该函数可以在安全的Rust中被调用;

下面这段代码使用slice时会崩溃

use std::slice;fn main() {let address = 0x01234usize;let r = address as *mut i32;let slice: &[i32]  = unsafe {slice::from_raw_parts_mut(r, 10000)};println!("slice = {:#?}", slice);
}

编译正确,运行崩溃
在这里插入图片描述

2.4 使用extern函数调用外部代码

  • extern函数可以创建和使用外部函数接口;
  • extern块中声明的函数在 Rust 代码中总是不安全的;
  • 外部函数接口是一个编程语言用以定义函数的方式,其允许不同(外部)编程语言调用这些函数。
extern "C" {fn abs(input: i32) -> i32;
}fn main() {unsafe {println!("Absolute value of -3 according to C: {}", abs(-3));}
}
  • 上述代码展示了如何集成C标准库中的abs函数;
  • extern "C"块中,列出了要调用的C语言中的外部函数;

从其它语言调用Rust函数

  • 使用extern创建一个允许其他语言调用 Rust 函数的接口;
  • 还需增加#[no_mangle]标注;
    • Mangling是指当编译器将代码中指定的函数名进改时会增加一些额外的信息;
    • 每一个编程语言的编译器都会以稍微不同的方式mangle函数名;
#[no_mangle]
pub extern "C" fn call_from_c() {println!("Just called a Rust function from C!");
}
  • 将上述代码编译为动态库并从C语言中链接,call_from_c函数就能够在 C 代码中访问;
  • extern 的使用无需使用unsafe标注;

2.5 访问或修改可变静态变量

  • 如果有两个线程访问相同的可变全局变量,则可能会造成数据竞争;
  • 全局变量在 Rust 中被称为 静态(static)变量
  • 通常静态变量的名称采用SCREAMING_SNAKE_CASE写法;
  • 静态变量只能储存拥有static生命周期的引用,因此静态变量的生命周期可以被Rust自动计算;
  • 访问不可变静态变量是安全的,访问和修改可变静态变量都是不安全的;
static mut COUNTER: u32 = 0;
static HELLO_WORLD: &str = "Hello, world!";fn add_to_count(inc: u32) {unsafe {COUNTER += inc;}
}fn main() {add_to_count(3);unsafe {println!("COUNTER: {}", COUNTER);}println!("name is: {}", HELLO_WORLD);
}
  • 代码展示了不可变静态变量HELLO_WORLD的声明和使用;
  • 任何访问或修改可变静态变量COUNTER的代码都必须位于unsafe中;

2.6 实现不安全trait

  • 当 trait 中至少有一个方法中包含编译器无法验证的不变式(invariant)时 trait 是不安全的;
  • 在 trait 之前增加 unsafe 关键字将 trait 声明为 unsafe,同时 trait 的实现也应该标记为unsafe;

2.7 访问联合体中的字段

参考文档:https://rustwiki.org/zh-CN/reference/items/unions.html

三、高级trait

3.1 关联类型在trait定义中指定占位符类型

  • 关联类型(associated types) 是一个将类型占位符与 trait 相关联的方式,这样 trait 的方法参数中就可以使用这些占位符类型;
  • trait的实现者会针对特定的实现在这个类型的位置指定相应的具体类型;
  • 标准库提供的Iterator trait就是一个带有关联类型的trait,内部的关联类型Item替代遍历的值的类型;
pub trait Iterator {type Item;fn next(&mut self) -> Option<Self::Item>;
}
  • Item是占位符,next的返回值说明它返回Option<Self::Item>类型的值;
  • trait的实现者指定Item的具体类型;
  • 用起来像泛型;

3.2 关联类型与泛型的区别

泛型关联类型
每次实现Trait时标注类型无需标注类型
可以为一个类型多次实现某个Trait(不同的泛型参数)无法为单个类型多次实现某个 Trait
pub trait Iterator2<T>{fn next(&mut self) -> Option<T>;
}struct Counter{}impl Iterator2<String> for Counter{fn next(&mut self) -> Option<String> {None}
}impl Iterator2<u32> for Counter{fn next(&mut self) -> Option<u32> {None}
}
  • 上述代码为 Counter实现了Iterator2的trait,返回值为String和u32;
pub trait Iterator{type Item;fn next(&mut self) -> Option<Self::Item>;
}struct Counter{}impl Iterator for Counter{type Item = u32;fn next(&mut self) -> Option<Self::Item> {None    }
}
  • 上述代码实现了关联类型,只能写一个,如果再写一遍impl Iterator for Counter{,里面的Item写成String,就会报错;

3.3 默认泛型类型参数和运算符重载

  • 可以在使用泛型参数的时候为泛型指定一个默认的具体类型;
  • 语法为:<PlaceholderType=ConcreteType>
  • 这种技术通常用于运算符重载
  • Rust不允许创建自己的运算符及重载任意的运算符;
  • 但可以通过std::ops中所列出的运算符和相应的 trait重载一部分相应的运算符;
use std::ops::Add;#[derive(Debug, PartialEq)]
struct Point {x: i32,y: i32,
}impl Add for Point {type Output = Point;fn add(self, other: Point) -> Point {Point {x: self.x + other.x,y: self.y + other.y,}}
}fn main() {assert_eq!(Point { x: 1, y: 0 } + Point { x: 2, y: 3 },Point { x: 3, y: 3 });
}
  • 上述代码在Point结构体上实现Add trait来重载+运算符;
  • Add trait定义如下
#[doc(alias = "+")]
#[const_trait]
pub trait Add<Rhs = Self> {/// The resulting type after applying the `+` operator.#[stable(feature = "rust1", since = "1.0.0")]type Output;#[must_use = "this returns the result of the operation, without modifying the original"]#[rustc_diagnostic_item = "add"]#[stable(feature = "rust1", since = "1.0.0")]fn add(self, rhs: Rhs) -> Self::Output;
}
  • Rhs = Self默认类型参数
  • RHS是一个泛型类型参数,它用于定义 add 方法中的 rhs 参数;
  • 如果实现 Add trait 时不指定 RHS 的具体类型,RHS 的类型将是默认的 Self 类型,也就是Point;
  • 下面是一个实现Add trait时使用自定义类型而不是默认类型的例子;
use std::ops::Add;struct Millimeters(u32);
struct Meters(u32);impl Add<Meters> for Millimeters {type Output = Millimeters;fn add(self, other: Meters) -> Millimeters {Millimeters(self.0 + (other.0 * 1000))}
}
  • 这是将毫米和米相加的例子;
  • 所以用Add<Meters>指明是米,相加的时候传进来的是米,所以乘以1000再相加;
  • 返回值是毫米;

默认泛型参数的主要应用场景

  • 扩展一个类型而不破坏现有代码;
  • 允许在大部分用户都不需要的特定场景下进行自定义;

3.4 完全限定语言与消歧义:如何调用相同名称的方法

trait Pilot {fn fly(&self);
}trait Wizard {fn fly(&self);
}struct Human;impl Pilot for Human {fn fly(&self) {println!("This is your captain speaking.");}
}impl Wizard for Human {fn fly(&self) {println!("Up!");}
}impl Human {fn fly(&self) {println!("*waving arms furiously*");}
}fn main() {let person = Human;person.fly();
}
  • PilotWizard都有fly方法;
  • Human又定义了一个fly
  • 几个方法的参数是完全相同的,那么调用哪个?
    在这里插入图片描述
  • 所以调用的是Human本身的fly方法;
  • 如下代码演示了调用Pilot和Wizard的fly方法;
fn main() {let person = Human;Pilot::fly(&person);Wizard::fly(&person);person.fly();
}
  • 当同一作用域的两个类型都实现了同一trait,Rust就不能明确的知道调用哪个函数;
  • 使用完全限定语法(fully qualified syntax) 可以解决这个问题;
  • 语法为:<Type as Trait>::function(receiver_if_method, next_arg, ...);
    • 可以在任何调用函数或方法的地方使用;
    • 允许忽略那些从其它上下文能推导出来的部分;
    • 当Rust无法区分代码编写人员期望调用哪个具体实现时,才需要使用这种语法;
trait Animal {fn baby_name() -> String;
}struct Dog;impl Dog {fn baby_name() -> String {String::from("Spot")}
}impl Animal for Dog {fn baby_name() -> String {String::from("puppy")}
}fn main() {println!("A baby dog is called a {}", Dog::baby_name()); //A baby dog is called a Spot//println!("A baby dog is called a {}", Animal::baby_name());
}
  • Animal trait有关联函数baby_name,结构体 Dog 实现了 Animal,都是一些关联方法(没有self);
  • baby_name直接在Dog之上,因此使用Dog::baby_name直接调用就可以;
  • 放开最后一个println!,则无法编译通过;
    在这里插入图片描述
  • 使用完全限制语法解决它:println!("A baby dog is called a {}", <Dog as Animal>::baby_name());

3.5 使用supertrait来要求trait附带其它trait的功能

  • 需要在一个trait中使用其它trait的功能;
    • 需要被依赖的trait也被实现
    • 那个被间接依赖的trait就是当前trait的supertrait;
use std::fmt;trait OutlinePrint: fmt::Display {fn outline_print(&self) {let output = self.to_string();let len = output.len();println!("{}", "*".repeat(len + 4));println!("*{}*", " ".repeat(len + 2));println!("* {} *", output);println!("*{}*", " ".repeat(len + 2));println!("{}", "*".repeat(len + 4));}
}struct Point {x: i32,y: i32,
}impl OutlinePrint for Point {}
  • OutlinePrint trait里的outline_print函数要被使用,则必须实现fmt::Displaytrait;
  • 结构体Point实现了OutlinePrint,但它没有实现Displaytrait,所以会报错;
    在这里插入图片描述
  • 在Point上实现了Display后就会通过编译;
impl fmt::Display for Point {fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {write!(f, "({}, {})", self.x, self.y)}
}

3.6 使用newtype模式用于在外部类型上实现外部trait

  • 孤独规则: 只有当trait或类型定义在本地包时,才能为该类型实现这个trait;
  • 可以通过newtype模式绕过这个规则;
    • 利用tuple struct元组结构体创建一个新的类型;
use std::fmt;struct Wrapper(Vec<String>);impl fmt::Display for Wrapper {fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {write!(f, "[{}]", self.0.join(", "))}
}fn main() {let w = Wrapper(vec![String::from("hello"), String::from("world")]);println!("w = {}", w);
}
  • 想在Vec<T>上实现Display确被孤独规则阻止(Display trait和Vec<T>都定义于外面的包中);
  • 可以创建一个包含Vec<T>实例的 Wrapper 结构体, 在它之上实现 Display 并使用Vec<T>的值;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/860556.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【昇思初学入门】第七天打卡-模型训练

训练模型 学习心得 构建数据集。这通常包括训练集、验证集&#xff08;可选&#xff09;和测试集。训练集用于训练模型&#xff0c;验证集用于调整超参数和监控过拟合&#xff0c;测试集用于评估模型的泛化能力。 &#xff08;mindspore提供数据集https://www.mindspore.cn/d…

使用Python和NLTK进行NLP分析的高级指南

在本文中&#xff0c;将利用数据集来比较和分析自然语言。 本文涵盖的基本构建块是&#xff1a; WordNet和同义词集相似度比较树和树岸命名实体识别 WordNet和同义词集 WordNet是NLTK中的大型词汇数据库语料库。WordNet维护与名词&#xff0c;动词&#xff0c;形容词&#…

Unity 弧形图片位置和背景裁剪

目录 关键说明 Unity 设置如下 代码如下 生成和部分数值生成 角度转向量 计算背景范围 关键说明 效果图如下 来自红警ol游戏内的截图 思路&#xff1a;确定中心点为圆的中心点 然后 计算每个的弧度和距离 Unity 设置如下 没什么可以说的主要是背景图设置 代码如下 …

攻克PS之路——Day1(A1-A8)

#暑假到了&#xff0c;作为可能是最后一个快乐的暑假&#xff0c;我打算学点技能来傍身&#xff0c;首先&#xff0c;开始PS之旅 这个帖子作为我跟着B站up主学习PS的记录吧&#xff0c;希望我可以坚持下去&#xff01; 学习的链接在这里&#xff1a;A02-PS软件安装&#xff0…

基于SSM+VUE的网上订餐系统(带1w+文档)

基于SSMVUE的网上订餐系统(带1w文档) 网上订餐系统的数据库里面存储的各种动态信息&#xff0c;也为上层管理人员作出重大决策提供了大量的事实依据。总之&#xff0c;网上订餐系统是一款可以真正提升管理者的办公效率的软件系统。 项目简介 基于SSMVUE的网上订餐系统(带1w文档…

亚马逊云科技官方活动:一个月拿下助理架构师SAA+云从业者考试认证(送半价折扣券)

为了帮助大家考取AWS SAA和AWS云从业者认证&#xff0c;小李哥争取到了大量考试半价50%折扣券&#xff0c;使用折扣券考试最多可省75刀(545元人民币)。 领取折扣券需要加入云师兄必过班群&#xff0c;在群中免费领取。目前必过班群招募到了超过200名小伙伴&#xff0c;名额有限…

从0到1使用vite搭建react项目保姆级教程(持续更新中)

一、vite创建react项目 要使用Vite创建一个React项目&#xff0c;你需要按照以下步骤操作&#xff1a; 1、确保你已经安装了Node.js&#xff08;建议使用最新的稳定版本&#xff09;。 2、 使用npm命令安装Vite CLI工具&#xff0c;再来创建项目 npm create vitelatest my-vi…

解决ChatGPT遇到“抱歉,我无法完成你的请求”问题

在使用ChatGPT时&#xff0c;可能会遇到这样的问题&#xff1a;当多次重复输入相同的内容时&#xff0c;系统会返回 抱歉&#xff0c;我无法完成你的请求 。本文将解释为什么会出现这种情况&#xff0c;并提供一些避免这种情况的解决方法。 为什么会出现“抱歉&#xff0c;我…

TSLANet:时间序列模型的新构思

实时了解业内动态&#xff0c;论文是最好的桥梁&#xff0c;专栏精选论文重点解读热点论文&#xff0c;围绕着行业实践和工程量产。若在某个环节出现卡点&#xff0c;可以回到大模型必备腔调或者LLM背后的基础模型重新阅读。而最新科技&#xff08;Mamba,xLSTM,KAN&#xff09;…

2024-6-20 Windows AndroidStudio SDK(首次加载)基础配置,SDK选项无法勾选,以及下载失败的一些解决方法

2024-6-20 Windows AndroidStudio SDK(首次加载)基础配置,SDK选项无法勾选,以及下载失败的一些解决方法 注意:仅仅是SDK这种刚安装时的配置的下载,不要和开源库的镜像源扯到一起&#xff01;&#xff01;&#xff01;&#xff01; 最近想玩AndroidStudio的JNI开发, 想着安装后…

Java三层框架的解析

引言&#xff1a;欢迎各位点击收看本篇博客&#xff0c;在历经很多的艰辛&#xff0c;我也是成功由小白浅浅进入了入门行列&#xff0c;也是收货到很多的知识&#xff0c;每次看黑马的JavaWeb课程视频&#xff0c;才使一个小菜鸡见识到了Java前后端是如何进行交互访问的&#x…

项目实训-vue(十二)

项目实训-vue&#xff08;十二&#xff09; 文章目录 项目实训-vue&#xff08;十二&#xff09;1.概述2.处理进度可视化 1.概述 本篇博客将记录我在图片上传页面中的工作。 2.处理进度可视化 除了导航栏之外&#xff0c;我们还需要对上传图片以及图片处理的过程以及流程进行…

数据结构-----【链表:刷题】

-------------------------------------------基础题参照leetcode---------------------------------------------------------------------------------------------------------- 【2】两数相加 /*** Definition for singly-linked list.* struct ListNode {* int val;…

浦语·灵笔2 模型部署图片理解实战

效果图镇楼 1、使用 huggingface_hub 下载模型中的部分文件&#xff08;演示练习与模型实战无关&#xff09; 使用 Hugging Face 官方提供的 huggingface-cli 命令行工具。安装依赖: pip install -U huggingface_hub 然后新建 python 文件&#xff0c;填入以下代码&#xf…

upload-labs第14关

upload-labs第14关 第十四关一、源代码分析代码审计 二、绕过分析a. 制作图片码首先需要一个照片&#xff0c;然后其次需要一个eval.php。 b.上传图片码上传成功 c.结合文件包含漏洞进行访问访问&#xff1a;http://192.168.1.110/upload-labs-master/include.php?filehttp://…

封装了一个iOS联动滚动效果

效果图 实现逻辑和原理 就是在 didEndDisplayingCell 方法中通过indexPathsForVisibleItems 接口获取当前可见的cell对应的indexPath&#xff0c; 然后获取到item最小的那一个&#xff0c;即可&#xff0c;同时&#xff0c;还要在 willDisplayCell 方法中直接设置标题的选中属…

cropperjs 裁剪/框选图片

1.效果 2.使用组件 <!-- 父级 --><Cropper ref"cropperRef" :imgUrl"url" searchImg"searchImg"></Cropper>3.封装组件 <template><el-dialog :title"title" :visible.sync"dialogVisible" wi…

Steam怎么卸载DLC Steam怎么只卸载DLC不卸载游戏教程

我们玩家在steam中玩游戏&#xff0c;有一个功能特别重要&#xff0c;那就是DLC&#xff0c;其实也就是一款游戏的扩展&#xff0c;很多游戏都有DLC&#xff0c;让游戏玩法特别丰富&#xff0c;比如都市天际线的DLC&#xff0c;给城市中就增加了很多建筑&#xff0c;或者更便捷…