26.高级特性(上)

news2024/11/23 23:52:25

目录

  • 一、不安全的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.coloradmin.cn/o/1862444.html

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

相关文章

沙姆镜头标定与重建

沙姆定律&#xff08; Scheimpflug principle&#xff09;则可以保证测量平面的物体能够清晰成像&#xff0c; 因此能够起到调整景深区域位置的作用。Scheimpflug 镜头就是根据沙姆定律所设计的一种特殊的镜头&#xff0c;通过机械结构使镜头与相机本体发生一定程度的偏转&…

网络爬虫Xpath开发工具的使用

开发人员在编写网络爬虫程序时若遇到解析网页数据的问题&#xff0c;则需要花费大量的时间编 写与测试路径表达式&#xff0c;以确认是否可以解析出所需要的数据。为帮助开发人员在网页上直接 测试路径表达式是否正确&#xff0c;我们在这里推荐一款比较好用的 XPath 开发工…

vue:响应式原理解析,深入理解vue的响应式系统

一、文章秒读 vue的响应式系统核心有两个&#xff0c;简单描述就是&#xff1a; 1.在数据变化时重新render依赖相关函数&#xff08;组件&#xff09;。 2.在vue2和vue3中分别使用Object.defineProperty和Proxy进行对象属性的读写。 数据变化时&#xff1a; 二、什么是响应…

123.网络游戏逆向分析与漏洞攻防-邮件系统数据分析-收邮件功能的完善与优化

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 如果看不懂、不知道现在做的什么&#xff0c;那就跟着做完看效果&#xff0c;代码看不懂是正常的&#xff0c;只要会抄就行&#xff0c;抄着抄着就能懂了 内容…

DDD学习笔记一

DDD的基本原则 &#xff08;1&#xff09;保持语言、模型、代码三者一致 语言&#xff1a;开发团队与领域专家沟通使用的自然语言。因为它与设计模型、代码是一致的&#xff0c;所以也称为通用语言。 模型&#xff1a;设计的输出物&#xff0c;是对领域逻辑的精准建模。模型会…

多供应商食品零售商城系统的会员营销设计和实现

在多供应商食品零售商城系统中&#xff0c;会员营销是提升用户粘性和增加销售的重要手段。一个有效的会员营销系统能够帮助平台更好地了解用户需求&#xff0c;提供个性化服务&#xff0c;进而提高用户满意度和忠诚度。本文将详细探讨多供应商食品零售商城系统的会员营销设计与…

LeetCode 算法:二叉树的层序遍历 c++

原题链接&#x1f517;&#xff1a;二叉树的层序遍历 难度&#xff1a;中等⭐️⭐️ 题目 给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问所有节点&#xff09;。 示例 1&#xff1a; 输入&#xff1a;roo…

工控必备C#

微软的C# 语言&#xff1f; QT 熟了以后,Qt 更方便些 方法Signal Slot 感觉上一样 现在更推荐PyQt 来构建,底层还是Qt C 的那些库,Qt 的开源协议有点狗

前端技术栈学习:Vue2、Vue cli脚手架、ElementUI组件库、Axios

1 基本介绍 &#xff08;1&#xff09;Vue 是一个前端框架, 易于构建用户界面 &#xff08;2&#xff09;Vue 的核心库只关注视图层&#xff0c;不仅易于上手&#xff0c;还便于与第三方库或项目整合 &#xff08;3&#xff09;支持和其它类库结合使用 &#xff08;4&#…

Python——Flask开发框架基础使用介绍

目录 Flask简介 安装 Flask 创建一个简单的 Flask 应用 运行你的Flask应用 添加模板和静态文件 使用静态文件 处理表单和数据 使用 Flask 扩展 结论 Flask简介 Flask 是一个轻量级的 Python Web 框架&#xff0c;它以其简洁和灵活的特点广受欢迎。Flask 让开发者能够快…

Hi3861 OpenHarmony嵌入式应用入门--中断按键

本篇讲解gpio的中断使用方式。 硬件原理图如下&#xff0c;与上一篇一样的电路 GPIO API API名称 说明 hi_u32 hi_gpio_init(hi_void); GPIO模块初始化 hi_u32 hi_io_set_pull(hi_io_name id, hi_io_pull val); 设置某个IO上下拉功能。 hi_u32 hi_gpio_set_dir(hi_gpio_…

6-47选择整数计算

整数计算&#xff1a; 用swing组件来实现整数计算&#xff0c;需要对整数计算的值进行校验。 import javax.swing.*; import java.awt.*; import java.awt.event.*;public class IntegerCalculator extends JFrame implements ActionListener {private JCheckBox[] checkBoxe…

【Docker】安装和加速

目录 1.安装 2.了解 docker 信息 3.查询状态 4. 重新启动Docker 1.安装 yum install –y docker 2.了解 docker 信息 cat /etc/redhat-release 3.查询状态 systemctl status docker 4.支持 1.12 的 docker 镜像加速 sudo mkdir -p /etc/docker sudo tee /etc/docke…

照片放大工具Topaz Gigapixel AI for Mac v7.1.2

Topaz Gigapixel AI软件是一款相当高效的PC端图像大小调整工具&#xff0c;更是一款能够为摄影师、设计师以及图像处理爱好者带来革命性体验的强大软件。它凭借先进的深度学习技术&#xff0c;打破了传统图像大小调整的限制&#xff0c;实现了真正意义上的无损放大和图像恢复。…

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

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

CSS|01 CSS简介CSS的3种书写方式注释

CSS简介 什么是CSS CSS&#xff08;Cascading Style Sheet&#xff09;&#xff0c;层叠样式表 或者 级联样式表&#xff0c;简称样式表。CSS的作用 主要用来给 HTML网页 设置外观或者样式。CSS的语法规则 h1 {属性:属性值}注意&#xff1a;1. CSS代码是由选择器和一对括号…

虚拟机装入kali linux

VMware 首先需要先安装VMware Workstation Pro可以根据这篇文章来下载VMware 下载kali linux Installer Images VS Virtual Machines Installer Images&#xff08;安装镜像&#xff09;Virtual Machines&#xff08;虚拟机&#xff09; 直接访问硬件&#xff0c;定制内核…

JupyterLab使用指南(七):JupyterLab使用 LaTeX 生成数学公式

在 JupyterLab 中&#xff0c;可以使用 LaTeX 语法生成复杂的数学公式。JupyterLab 内置对 LaTeX 的支持&#xff0c;使得我们可以方便地在 notebook 中编写和展示数学公式。以下是详细的步骤和示例。 1. 使用 LaTeX 生成数学公式 LaTeX 是一种专门用于排版数学公式的语言。J…

springboot+vue+mybatis门窗管理系统+PPT+论文+讲解+售后

如今社会上各行各业&#xff0c;都在用属于自己专用的软件来进行工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。互联网的发展&#xff0c;离不开一些新的技术&#xff0c;而新技术的产生往往是为了解决现有问题而产生的。针对于仓库信息管理方…

如何使用 Swift 中的 GraphQL

文章目录 前言基础知识ApolloGraphQL结论前言 我一直在分享关于类型安全和在 Swift 中构建健壮 API 的更多内容。今天,我想继续探讨类型安全的话题,介绍 GraphQL。GraphQL 是一种用于 API 的查询语言。本周,我们将讨论 GraphQL 的好处,并学习如何在 Swift 中使用它。 基础…