首页 > 编程语言 >Rust编程语言入门之智能指针

Rust编程语言入门之智能指针

时间:2023-04-16 20:59:04浏览次数:31  
标签:Cons 编程语言 let fn Rc RefCell new Rust 指针

智能指针

智能指针(序)

相关的概念

  • 指针:一个变量在内存中包含的是一个地址(指向其它数据)
  • Rust 中最常见的指针就是”引用“
  • 引用:
    • 使用 &
    • 借用它指向的值
    • 没有其余开销
    • 最常见的指针类型

智能指针

  • 智能指针是这样一些数据结构:
    • 行为和指针相似
    • 有额外的元数据和功能

引用计数(Reference counting)智能指针类型

  • 通过记录所有者的数量,使一份数据被多个所有者同时持有
  • 并在没有任何所有者时自动清理数据

引用和智能指针的其它不同

  • 引用:只借用数据
  • 智能指针:很多时候都拥有它所指向的数据

智能指针的例子

  • String 和 Vec<T>

  • 都拥有一片内存区域,且允许用户对其操作

  • 还拥有元数据(例如容量等)

  • 提供额外的功能或保障(String 保障其数据是合法的 UTF-8 编码)

智能指针的实现

  • 智能指针通常使用 Struct 实现,并且实现了:
    • Deref 和 Drop 这两个 trait
  • Deref trait:允许智能指针 struct 的实例像引用一样使用
  • Drop trait:允许你自定义当智能指针实例走出作用域时的代码

本章内容

  • 介绍标准库中常见的智能指针
    • Box<T>:在 heap 内存上分配值
    • Rc<T>:启用多重所有权的引用计数类型
    • Ref<T>RefMut<T>,通过 RefCell<T>访问:在运行时而不是编译时强制借用规则的类型
  • 此外:
    • 内部可变模型(interior mutability pattern):不可变类型暴露出可修改其内部值的 API
    • 引用循环(reference cycles):它们如何泄露内存,以及如何防止其发生。

一、使用Box<T> 来指向 Heap 上的数据

Box<T>

  • Box<T> 是最简单的智能指针:
    • 允许你在 heap 上存储数据(而不是 stack)
    • stack 上是指向 heap 数据的指针
    • 没有性能开销
    • 没有其它额外功能
    • 实现了 Deref trait 和 Drop trait

Box<T> 的常用场景

  • 在编译时,某类型的大小无法确定。但使用该类型时,上下文却需要知道它的确切大小。
  • 当你有大量数据,想移交所有权,但需要确保在操作时数据不会被复制。
  • 使用某个值时,你只关心它是否实现了特定的 trait,而不关心它的具体类型。

使用Box<T>在heap上存储数据

fn main() {
  let b = Box::new(5);
  println!("b = {}", b);
} // b 释放存在 stack 上的指针 heap上的数据

使用 Box 赋能递归类型

  • 在编译时,Rust需要知道一个类型所占的空间大小
  • 而递归类型的大小无法再编译时确定
  • 但 Box 类型的大小确定
  • 在递归类型中使用 Box 就可解决上述问题
  • 函数式语言中的 Cons List

关于 Cons List

  • Cons List 是来自 Lisp 语言的一种数据结构
  • Cons List 里每个成员由两个元素组成
    • 当前项的值
    • 下一个元素
  • Cons List 里最后一个成员只包含一个 Nil 值,没有下一个元素 (Nil 终止标记)

Cons List 并不是 Rust 的常用集合

  • 通常情况下,Vec 是更好的选择
  • (例子)创建一个 Cons List
use crate::List::{Cons, Nil};

fn main() {
  let list = Cons(1, Cons(2, Cons(3, Nil)));
}

enum List {  // 报错
  Cons(i32, List),
  Nil,
}
  • (例)Rust 如何确定为枚举分配的空间大小
enum Message {
  Quit,
  Move {x: i32, y: i32},
  Write(String),
  ChangeColor(i32, i32, i32),
}

使用 Box 来获得确定大小的递归类型

  • Box 是一个指针,Rust知道它需要多少空间,因为:
    • 指针的大小不会基于它指向的数据的大小变化而变化
use crate::List::{Cons, Nil};

fn main() {
  let list = Cons(1, 
    Box::new(Cons(2, 
      Box::new(Cons(3, 
        Box::new(Nil))))));
}

enum List {  
  Cons(i32, Box<List>),
  Nil,
}
  • Box
    • 只提供了”间接“存储和 heap 内存分配的功能
    • 没有其它额外功能
    • 没有性能开销
    • 适用于需要”间接“存储的场景,例如 Cons List
    • 实现了 Deref trait 和 Drop trait

二、Deref Trait(1)

Deref Trait

  • 实现 Deref Trait 使我们可以自定义解引用运算符 * 的行为。
  • 通过实现 Deref,智能指针可像常规引用一样来处理

解引用运算符

  • 常规引用是一种指针
fn main() {
  let x = 5;
  let y = &x;
  
  assert_eq!(5, x);
  assert_eq!(5, *y);
}

Box<T> 当作引用

  • Box<T> 可以替代上例中的引用
fn main() {
  let x = 5;
  let y = Box::new(x);
  
  assert_eq!(5, x);
  assert_eq!(5, *y);
}

定义自己的智能指针

  • Box<T> 被定义成拥有一个元素的 tuple struct
  • (例子)MyBox<T>
struct MyBox<T>(T);

impl<T> MyBox<T> {
  fn new(x: T) -> MyBox<T> {
    MyBox(x)
  }
}

fn main() {
  let x = 5;
  let y = MyBox::new(x);  // 报错
  
  assert_eq!(5, x);
  assert_eq!(5, *y);
}

实现 Deref Trait

  • 标准库中的 Deref trait 要求我们实现一个 deref 方法:
    • 该方法借用 self
    • 返回一个指向内部数据的引用
  • (例子)
use std::ops::Deref;

struct MyBox<T>(T);

impl<T> MyBox<T> {
  fn new(x: T) -> MyBox<T> {
    MyBox(x)
  }
}

impl<T> Deref for MyBox<T> {
  type Target = T;
  
  fn deref(&self) -> &T {
    &self.0
  }
}

fn main() {
  let x = 5;
  let y = MyBox::new(x);  
  
  assert_eq!(5, x);
  assert_eq!(5, *y);  // *(y.deref())
}

三、Deref Trait (2)

函数和方法的隐式解引用转化(Deref Coercion)

  • 隐式解引用转化(Deref Coercion)是为函数和方法提供的一种便捷特性
  • 假设 T 实现了 Deref trait:
    • Deref Coercion 可以把 T 的引用转化为 T 经过 Deref 操作后生成的引用
  • 当把某类型的引用传递给函数或方法时,但它的类型与定义的参数类型不匹配:
    • Deref Coercion 就会自动发生
    • 编译器会对 deref 进行一系列调用,来把它转为所需的参数类型
      • 在编译时完成,没有额外性能开销
use std::ops::Deref;

fn hello(name: &str) {
  println!("Hello, {}", name);
}

fn main() {
  let m = MyBox::new(String::from("Rust"));
  
  // &m &MyBox<String> 实现了 deref trait
  // deref &String
  // deref &str
  hello(&m);
  hello(&(*m)[..]);
  
  hello("Rust");
}

struct MyBox<T>(T);

impl<T> MyBox<T> {
  fn new(x: T) -> MyBox<T> {
    MyBox(x)
  }
}

impl<T> Deref for MyBox<T> {
  type Target = T;
  
  fn deref(&self) -> &T {
    &self.0
  }
}

fn main() {
  let x = 5;
  let y = MyBox::new(x);  
  
  assert_eq!(5, x);
  assert_eq!(5, *y);  // *(y.deref())
}

解引用与可变性

  • 可使用 DerefMut trait 重载可变引用的 * 运算符
  • 在类型和 trait 在下列三种情况发生时,Rust会执行 deref coercion:
    • 当 T:Deref<Target=U>,允许 &T 转换为 &U
    • 当 T:DerefMut<Target=U>,允许 &mut T 转换为 &mut U
    • 当 T:Deref<Target=U>,允许 &mut T 转换为 &U

四、Drop Trait

Drop Trait

  • 实现 Drop Trait,可以让我们自定义当值将要离开作用域时发生的动作。
    • 例如:文件、网络资源释放等
    • 任何类型都可以实现 Drop trait
  • Drop trait 只要求你实现 drop 方法
    • 参数:对self 的可变引用
  • Drop trait 在预导入模块里(prelude)
/*
 * @Author: QiaoPengjun5162 [email protected]
 * @Date: 2023-04-13 21:39:51
 * @LastEditors: QiaoPengjun5162 [email protected]
 * @LastEditTime: 2023-04-13 21:46:50
 * @FilePath: /smart/src/main.rs
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE 
 */
struct CustomSmartPointer {
    data: String,
}

impl Drop for CustomSmartPointer {
    fn drop(&mut self) {
        println!("Dropping CustomSmartPointer with data: `{}`!", self.data);
    }
}

fn main() {
    let c = CustomSmartPointer {data: String::from("my stuff")};
    let d = CustomSmartPointer {data: String::from("other stuff")};
    println!("CustomSmartPointers created.")
}

运行

smart on  master [?] is 

标签:Cons,编程语言,let,fn,Rc,RefCell,new,Rust,指针
From: https://www.cnblogs.com/QiaoPengjun/p/17324022.html

相关文章

  • 编程语言和编程工具
    使用过的语言:C、JavaC语言的优势:C语言编写的代码效率高,运行速度快。C语言的语法和功能简单,易于学习和理解。C语言具有很高的可移植性,可以在多种不同的平台上运行。Java语言的优势:Java语言是一种跨平台的编程语言,可以在不同的操作系统和硬件平台上运行。Java语言有很多强大......
  • 作业随笔-指针2
    函数指针,数组指针,指针数组,指向函数指针数组的指针,函数指针数组6题(*(char**(*)(char**,char**))0)(char**,char**)把0强制转换为函数地址指向某个函数,并解引用进行使用,改变函数地址,操作内存,减少内存浪费函数指针,回调函数数组元素排序,结构体排序#define_CRT_SECURE_NO_WARNINGS1#incl......
  • 2023年Rust发展如何?
    1.引言Rust是一种系统编程语言,它注重安全、并发和内存效率。自2010年首次发布以来,Rust一直在快速发展,吸引了越来越多的开发者加入其社区。Rust语言的设计目标是提供一种安全、并发和实用的语言,它可以满足系统编程的需求,同时也适用于其他领域。2.Rust在2022年的发展趋势在202......
  • Rust中的derive属性详解
    1.Rust中的derive是什么?在Rust语言中,derive是一个属性,它可以让编译器为一些特性提供基本的实现。这些特性仍然可以手动实现,以获得更复杂的行为。2.derive的出现解决了什么问题?derive属性的出现解决了手动实现一些特性时需要编写大量重复代码的问题。它可以让编译器自动生成......
  • Rust语言 学习10 测试
    一、编写测试cargo创建测试项目使用Clion打开工程,lib.rs代码如下然后运行这个测试看看效果增加一个单测#[test]fnnew_test(){panic!("maketestfail");}......
  • [oeasy]python00134_[趣味拓展]python起源_历史_Guido人生_ABC编程语言_Tanenbaum
    python历史回忆上次内容颜文字是kaomoji把字符变成一种图画的方法一层叠一层很多好玩儿的kaomoji是一层层堆叠起来的meme虚拟的表情也在真实世界有巨大影响一步步地影响字符编码就是这样一步步发展过来的python也是一步步发展到今天的python究竟是怎么发展的呢?......
  • [oeasy]python00134_[趣味拓展]python起源_历史_Guido人生_ABC编程语言_Tanenbaum
    python历史回忆上次内容颜文字是kaomoji把字符变成一种图画的方法一层叠一层很多好玩儿的kaomoji是一层层堆叠起来的meme ​ 添加图片注释,不超过140字(可选) 虚拟的表情也在真实世界有巨大影响一步步地影响 ​......
  • C指针——知识点集锦
    CPU访问内存时需要的是地址,而不是变量名和函数名!变量名和函数名只是地址的一种助记符,当源文件被编译和链接成可执行程序后,它们都会被替换成地址。编译和链接过程的一项重要任务就是找到这些名称所对应的地址。变量名表示的是数据本身,而函数名、字符串名和数组名表示的是代码块......
  • Moves, copies and clones in Rust
    原文链接:Moves,copiesandclonesinRust简介(Introduction)move和copy是Rust中的基础概念。这对于来自Ruby、Python或C#等垃圾回收语言的程序员来说可能是完全陌生的。这些术语在C++中也确实存在,但它们在Rust中的含义却有微妙的不同。在本文中,我将解释对值进行mo......
  • web空指针报500错误
     循环遍历数据遇到空指针,调试,发现转发没错,取出现问题。  后想起,需要先将数据查询,使得先进行转发再取,之前是直接取,所以为空。 将web配置后可行。 ......