< 返回版块

洋芋 发表于 2020-02-06 00:03

Tags:rust,每周一知

主要内容包括:

  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的引用规则为:

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

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

参见以下示例:

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的实现:

#[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的实现:

#[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>只需拷贝指针。

递归类型的经典示例:

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对象的示例:

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

评论区

写评论

还没有评论

1 共 0 条评论, 1 页