前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Rust每周一知】理解智能指针Box<T>

【Rust每周一知】理解智能指针Box<T>

作者头像
MikeLoveRust
发布2020-02-20 10:47:54
2K0
发布2020-02-20 10:47:54
举报

主要内容包括:

  1. 基础概念:指针,引用,智能指针
  2. 智能指针Box<T>DerefDrop

1. 基础概念

1.1 指针

指针是个通用概念,它表示内存地址这种类型,其引用或“指向”其他数据。Rust中的指针是“第一类公民”(first-class values),可以将它们移动或复制,存储到数据结构中并从函数中返回。Rust提供了多种类型的指针:

  • 引用(Reference),共享引用&,不可变引用&mut
  • 原生指针(Raw Pointer),*const*mut
  • 智能指针(Smart Pointer),Box<T>Rc<T>
1.2 引用

Rust中使用&符号表示引用,也叫引用操作符。其使用场景是只使用类型的值但不获取其所有权,同时Rust的引用规则为:

  • 在作用域中的数据有且只能有一个可变引用;
  • 可以有多个不可变引用;
  • 不能同时拥有不可变引用和可变引用。

注:一个引用的作用域从声明的地方开始一直持续到最后一次使用为止。

参见以下示例:

代码语言:javascript
复制
fn main() {
let arr = [1,2,3];
let addr = &arr; // 通过 & 得到引用,默认是不可变的
println!("{:p}", addr); // 内存地址
	
let mut vec = vec![1,2,3]; // 要获取可变引用,必须先声明可变绑定
let new_vec = &mut vec; // 通过 &mut 得到可变引用
	new_vec.push(4);
println!("{:?}", new_vec); // [1, 2, 3, 4]	
let mut str1 = String::from("hello");
let m1 = &mut str1;
let m2 = &mut str1; // ERROR:只能有一个可变引用
println!("{}, {}", m1, m2);
println!("{}", m2); // WORK:m1 作用域结束
	
let mut str2 = String::from("world");
let r1 = &str2;
let r2 = &str2; // 没问题
let r3 = &mut str2;
println!("{}, {}, and {}", r1, r2, r3); // ERROR:不能同时拥有不可变引用和可变引用
println!("{}", r3); // WORK:r1 和 r2 作用域结束
}

从语义上说,不管是&还是&mut,都是对原有变量的所有权的借用,所以引用也被称为借用。

1.3 智能指针

智能指针的概念起源于C++,智能指针是一类数据结构,他们的表现类似指针,但是拥有额外的元数据和功能。

在Rust中,引用和智能指针的一个的区别是引用是一类只借用数据的指针;相反,在大部分情况下,智能指针拥有他们指向的数据。Rust标准库中不同的智能指针提供了比引用更丰富的功能:

  • Box<T>,用于在堆上分配数据。
  • Rc<T>,一个引用计数类型,其数据可以有多个所有者。
  • Ref<T>RefMut<T>,通过RefCell<T>访问,一个在运行时而不是在编译时执行借用规则的类型。

2. 智能指针Box<T>

在Rust中,所有值默认都是栈上分配。通过创建Box<T>,可以把值装箱,使它在堆上分配。Box<T>类型是一个智能指针,因为它实现了Dereftrait,它允许Box<T>值被当作引用对待。当Box<T>值离开作用域时,由于它实现了Droptrait,首先删除其指向的堆数据,然后删除自身。

2.1 Deref

Deref这个trait,允许我们重载解引用运算符*。实现Deref的智能指针可以被当作引用来对待,也就是说可以对智能指针使用*运算符进行解引用。

Box<T>Deref的实现:

代码语言:javascript
复制
#[stable(feature = "rust1", since = "1.0.0")]
impl<T: ?Sized> Deref for Box<T> {
    type Target = T;

    fn deref(&self) -> &T {
        &**self
    }
}

该实现返回&**self。为什么呢?由于self是一个&Box<T>,因此对其进行一次解引用*将获得一个Box<T>,而第二次解引用*将获得一个T。最后,将其包装在引用&中并返回。

:如果是我们自定义的类型,要实现deref,则不能仿照它,否则会造成无限递归。

2.2 Drop

Drop这个trait的主要作用是释放实现者实例拥有的资源。它只有一个方法:drop,当实例离开作用域时会自动调用该方法,从而调用实现者指定的代码。

Box<T>Drop的实现:

代码语言:javascript
复制
#[stable(feature = "rust1", since = "1.0.0")]
unsafe impl<#[may_dangle] T: ?Sized> Drop for Box<T> {
    fn drop(&mut self) {
        // FIXME: Do nothing, drop is currently performed by compiler.
    }
}
2.3 Box<T>

Box<T>是堆上分配的指针类型,称为“装箱”(boxed),其指针本身在栈,指向的数据在堆,在Rust中提供了最简单的堆分配类型。使用Box<T>的情况:

  • 递归类型和trait对象。Rust需要在编译时知道一个类型占用多少空间,Box<T>的大小是已知的。
  • “大”的数据转移所有权。用Box<T>只需拷贝指针。

递归类型的经典示例:

代码语言:javascript
复制
use List::{Cons, Nil};

#[derive(Debug)]
enum List<T> {
    Cons(T, Box<List<T>>),
    Nil,
}

fn main() {
let recursive_list: List<i32> = Cons(1, Box::new(Cons(2, Box::new(Nil))));
println!("{:?}", recursive_list); // 打印出:Cons(1, Cons(2, Nil))
}

trait对象的示例:

代码语言:javascript
复制
trait T {
    fn m(&self) -> u64;
}
  
struct S {
    i: u64
}
  
impl T for S {
    fn m(&self) -> u64 { self.i }
}

fn f(x: Box<dyn T>) {
    println!("{}", x.m())
}
  
fn main() {
    let s = S{i : 100};
    println!("{}", s.m());

    let b: Box<S> = Box::new(S{i: 100});
    f(b);
}

本文示例代码:https://github.com/lesterli/rust-practice/tree/master/head-first/std-box

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-02-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Rust语言学习交流 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 基础概念
    • 1.1 指针
      • 1.2 引用
        • 1.3 智能指针
        • 2. 智能指针Box<T>
          • 2.1 Deref
            • 2.2 Drop
              • 2.3 Box<T>
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档