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.rhkb.cn/news/360550.html

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

相关文章

百元蓝牙耳机哪款好?值得购买的百元蓝牙耳机品牌有这些

在如今移动互联网时代&#xff0c;蓝牙耳机已经成为不少人生活中的必备产品。然而&#xff0c;市面上的蓝牙耳机种类繁多&#xff0c;价格也参差不齐&#xff0c;选择一款性价比高的产品成了不少消费者的难题。尤其是针对百元左右的蓝牙耳机&#xff0c;更是需要慎重选择。那么…

考研数学|张宇和武忠祥,强化能不能同时跟?

可以说你跟武老师学明白了&#xff0c;120完全没问题&#xff01;如果追求更高&#xff0c;宇哥的怀抱也想你敞开&#xff01; 学长我21年一战数学83&#xff0c;总分没过线&#xff0c;22年二战143&#xff0c;逆袭上岸211&#xff01;市面上的老师我基本都听过&#xff0c;最…

【vite】define 全局常量定义

&#x1f9ed; define 说明 类型&#xff1a; Record<string, any> 定义全局常量替换方式。其中每项在开发环境下会被定义在全局&#xff0c;而在构建时被静态替换。 Vite 使用 esbuild define 来进行替换&#xff0c;因此值的表达式必须是一个包含 JSON 可序列化值&a…

[MySql]两阶段提交

文章目录 什么是binlog使用binlog进行恢复的流程 什么是redolog缓冲池redologredolog结构 两阶段提交 什么是binlog binlog是二进制格式的文件&#xff0c;用于记录用户对数据库的修改&#xff0c;可以作用于主从复制过程中数据同步以及基于时间点的恢复&#xff08;PITR&…

Matlab|【防骗帖】考虑时空相关性的风电功率预测误差建模与分析

目录 1 主要内容 2 部分程序 3 下载链接 1 主要内容 这个程序《考虑时空相关性的风电功率预测误差建模与分析》画的图片非常漂亮&#xff0c;和原文献基本一致&#xff0c;但是实际上内容并未实现出来&#xff0c;主要就是利用现有的风电预测的数据和结果做了相关的图&#…

Verilog的逻辑系统及数据类型(一):四值逻辑系统

目录 1. Verilog采用的四值逻辑系统2.主要数据类型2.1 net&#xff08;线网&#xff09;2.2 寄存器类 &#xff08;register)2.3 Verilog中net和register声明语法2.3.1 net声明2.3.2 寄存器声明 2.4 选择正确的数据类型2.5 选择数据类型时常犯的错误2.5.1 信号类型确定方法总结…

目标检测数据集 - 手机屏幕表面表面缺陷检测数据集下载「包含VOC、COCO、YOLO三种格式」

数据集介绍&#xff1a;手机屏幕表面缺陷检测数据集&#xff0c;真实采集高质量手机屏幕表面含缺陷图片数据&#xff0c;数据集含多款不同型号和品牌的手机屏幕表面图片数据&#xff0c;包括苹果手机屏、三星手机屏、华为手机屏等数据。数据标注标签包括 Bubble 气泡/水滴、Scr…

现在的Java面试都这么扯淡了吗?

在开始前刚好我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「java的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“666”之后私信回复“666”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#xff01;开发兼过半年面试官 刚开始…

upload-labs第14关

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

VUE div的右上角的角标/标签

一、效果图 二、代码 <div class"comp-overview"><div class"overview-item" v-for"(item,index) in overviewInfoList" :key"index"><div class"angle_mark"><span>{{item.label}}</span>&…

重学java 73.设计模式

本想送你一本沉思录&#xff0c;可该迷途知返的人是我 —— 24.6.18 设计模式 设计模式(Design pattern)&#xff0c;是一套被反复使用、经过分类编目的、代码设计经验的总结&#xff0c;使用设计模式是为了可重用代码、保证代码可靠性、程序的重用性,稳定性。 1995 年&#x…

Python | Leetcode Python题解之第165题比较版本号

题目&#xff1a; 题解&#xff1a; class Solution:def compareVersion(self, version1: str, version2: str) -> int:n, m len(version1), len(version2)i, j 0, 0while i < n or j < m:x 0while i < n and version1[i] ! .:x x * 10 ord(version1[i]) - o…

HarmonyOS应用开发——Hello World

下载 HUAWEI DevEco Studio: https://developer.harmonyos.com/cn/develop/deveco-studio/#download 同意&#xff0c;进入配置页面&#xff1a; 配置下载源以及本地存放路径&#xff0c;包括nodejs和ohpm: 配置鸿蒙SDK路径&#xff1a; 接受协议&#xff1a; 确认无误后&#…

配置OSPF认证(华为)

#交换设备 配置OSPF认证-基于华为路由器 OSPF&#xff08;开放最短路径优先&#xff09;是一种内部网关协议&#xff08;IGP&#xff09;&#xff0c;用于在单一自治系统&#xff08;AS&#xff09;内决策路由。OSPF认证功能是路由器中的一项安全措施&#xff0c;它的主要用途…

【Docker】rancher 管理平台搭建

目录 1. 所有节点安装docker 2. 所有节点配置/etc/sysconfig/docker 文件修改如下配置 3. 配置证书 4. 镜像仓库导入镜像 5. 创建镜像仓库 5.1 查询上传的 image id 5.2 镜像打标签 5.3 镜像上推 6. server 节点 7. client 节点 8. 在 server 节点启动 9. 查看运行…

前端:HTML、CSS、JavaScript 代码注释 / 注释与代码规范

一、HTML 行内注释 HTML注释是在HTML代码中添加说明和解释的一种方法&#xff0c;这些注释不会被浏览器渲染或显示在页面上&#xff0c;而是被浏览器忽略。HTML注释对于代码的可读性、可维护性和团队协作非常重要。 1.1、HTML注释的语法 HTML注释的语法是以<!--开始&…

优化系统小工具

一款利用VB6编写的系统优化小工具&#xff0c;系统优化、桌面优化、清理垃圾、查找文件等功能。 下载:https://download.csdn.net/download/ty5858/89432367

Emacs之显示blame插件:blamer、git-messenger(一百四十四)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

云渲染农场使用指南:如何以最低成本享受最快渲染速度?

​云渲染农场怎么低成本享受快速渲染&#xff1f; 云渲染农场利用其分布式计算能力&#xff0c;为视觉艺术家提供了一种经济高效的渲染选择。它特别适用于高质量的影视动画和视觉效果制作。下面一起来看看如何以最低的成本实现快速渲染的策略。 在追求成本效益的同时&#xff…

引领AI新时代:深度学习与大模型的关键技术

文章目录 &#x1f4d1;前言一、内容概述二、作者简介三、书籍特色四、学习平台与资源 &#x1f4d1;前言 在数字化浪潮席卷全球的今天&#xff0c;人工智能&#xff08;AI&#xff09;和深度学习技术已经渗透到我们生活的方方面面。从智能手机中的智能语音助手&#xff0c;到…