rust入门_数据所有权与生命周期管理
所有权
rust的数据类型分为两种,一种是实现了copy trait的数据类型如i32 u32,一种是没有实现copy trait的数据类型,如String,在进行变量间的赋值操作时,实现了copy trait类型的数据类型会将数据复制一份过去。而没有实现copy trait的数据类型则会将数据的所有权转移,先前对数据进行引用的符号的生命周期就结束了
e.g.
fn main() {
let x: i32 = 5;
let y = x;
println!("x = {}, y = {}", x, y);
let s1 = String::from("hello");
let s2 = s1;
println!("{}", s2);
}
引用类型
rust的引用类型可分为可变引用和不可变引用,可以理解为编译期间进行严格生命周期及检查的”智能指针”,不可变引用对于数据只能做读的行为,可变引用可以做写的行为。使用引用可以在不转移所有权的情况下对对象进行读写操作。
使用方法 e.g
fn main() {
let mut value = 10;
// 不可变引用
let r1 = &value;
println!("{}", r1);
// 可变引用
let r2 = &mut value;
*r2 += 5;
println!("{}", value);
}
可变引用在进行写行为时需要显式解引用,这里相对c的指针来说有些差别
可变引用和不可变引用有严格的使用规定,同一作用域下 同一对象不能拥有两个可变引用,同一作用域下对于同一对象不能同时存在可变引用和不可变引用,通过对引用的使用进行限制来避免数据竞争。但这只是官方文档的说法,可以同一作用域下同时存在可变引用和不可变引用,以及多个可变引用,程序也可以编译成功,关键在于存在的情况下是否有使用可变引用,使用了就会编译错误。
e.g
fn main() {
let mut value = 10;
let r1 = &mut value;
let r2 = &mut value;
*r1 = 5;
*r2 = 10;
println!("{}", value);
}
在这个e.g. 同一作用域下value有一个不可变引用r1和可变引用r2,使用r2进行写的操作修改了value的值,那这个eg为什么不会报错?因为rust是非词法生命周期语言,对象的生命周期不是严格等同于词法环境,而是基于最后一次使用的位置,r1的生命周期在println!(“{}”, r1); 后就结束了
e.g
fn main() {
let mut value = 10;
let r1 = &value;
println!("{}", r1);
let r2 = &mut value;
*r2 += 5;
println!("{}", value);
}
如果在*r2+=5 使用后再对r1进行使用,那就会编译错误
e.g
fn main() {
let mut value = 10;
let r1 = &value;
println!("{}", r1);
let r2 = &mut value;
*r2 += 5;
println!("{}", value);
println!("{}",r1);
}
生命周期标注
rust可以显式的对对象的生命周期进行描述,生命周期标注是一种特殊的泛型参数,用于显式标注引用的存活时间来避免垂悬引用的出现。
通常是在结构体中包含引用类型的情况下需要显式的标注结构体引用字段的生命周期和对该字段进行初始化函数对应形参的生命周期相同,以及函数的返回引用类型的情况需要标注。函数需要返回引用类型的情况下,这个引用一定和形参的生命周期有关系,否则这个引用一定是垂悬引用。
显式标注引用以及可变引用的语法
&'a i32
&'a mut i32
使用样例
需要显式的对引用类型进行生命周期来source和parsed的生命周期和创建Cache对象时new传过去input的生命周期相同,不然会有可能会出现垂悬引用
struct Cache<'a> {
source: &'a str,
parsed: Vec<&'a str>,
}
impl<'a> Cache<'a> {
fn new(input: &'a str) -> Self {
let parsed = input.split_whitespace().collect();
Cache { source: input, parsed }
}
fn get(&self, index: usize) -> Option<&'a str> {
self.parsed.get(index).copied()
}
}
使用样例 2
select函数的返回值通过use_a 参数来决定返回 a引用还是b引用,a和b引用的生命周期可能不相同,如果没有显式标注a和b的生命周期,编译会报错。
fn select<'a, 'b>(a: &'a str, b: &'b str, use_a: bool) -> &'a str
where 'b: 'a
{
if use_a { a } else { b }
}
生命周期标注省略规则
函数或者方法的参数的生命周期被称为输入生命周期,返回值的生命周期被称为输出生命周期,编译器有三条规则用于检查对生命周期标注省略的函数或者方法,符合规则就可以编译成功,反之。
第一条规则是编译器为每一个引用参数都分配一个生命周期参数
第二条是如果只有一个输入生命周期参数,那么它被赋予所有的输出生命参数
第三条是如果有多个输入生命周期参数,并且其中一个参数是&self或&mut self,说明这是一个方法,所有的输出生命周期都被赋予self的生命周期
所以针对返回值是引用类型的函数,生命周期标注省略只适用于只有一个输入生命周期参数的函数,多个输入生命周期的方法。
RefCell内部可变性 &Rc智能指针& Arc智能指针
RefCell<T>
std::cell::RefCell是rust标准库里实现了内部可变性的一个智能指针。
内部可变性的原理是通过unsafe绕过了rust编译期间的借用规则检查,可以实现在运行时借用可变引用,即使定义时没有明确变量是mut的。refcell会在运行时检查借用规则,内部提供的方法还是遵循rust的借用规则的,refcell并不是线程安全的。
e.g. 通过refcell提供的功能修改不可变绑定data的值
use std::cell::RefCell;
fn main() {
let data = RefCell::new(10);
*data.borrow_mut() += 1;
println!("{}",data.borrow());
}
RefCell<T>
智能指针常常和Rc<T>
或 Arc<T>
智能指针结合去实现相同数据有多个所有者,并且每个所有者都可以更改数据的功能。可以做到在同一作用域下共享所有权还能修改数据。
Rc<T>
&Arc<T>
Rc<T>
和 Arc<T>
是rust标准库的两个智能指针,可以共将一个数据的所有权共享给多个所有者,引用类型只是借用数据。可以拥有数据的所有权代表可以管理这块数据的生命周期,但Rc和Arc并不能做到写数据,如果能做到那就会存在数据竞争了。
e.g. 通过Rc 可以延长数据的所有权
use std::rc::Rc;
#[derive(Debug)]
struct A {
data: Rc<String>,
}
fn create() -> A {
let s = Rc::new(String::from("ciallo"));
A { data: Rc::clone(&s) }
}
fn main() {
let data = create();
println!("{:?}",data);
}
Rc<Box<T>>
这样的结构,在定义时无法指定Box是可变的,只能做到指定Rc具有可变性。所以才需要内部可变性这个东西,也就是用refcell来配合 Rc<RefCell<T>>
let mut data = Rc::new(Box::new(10));
e.g. Rc<RefCell<T>>
use std::{cell::RefCell, rc::Rc};
fn main() {
let mut data = Rc::new(RefCell::new(10));
let r1 = Rc::clone(&data);
let r2 = Rc::clone(&data);
*r1.borrow_mut() = 11;
println!("{}",data.borrow());
*r2.borrow_mut() = 12;
println!("{}",data.borrow());
}
裸指针
裸指针是一个不受借用规则限制 c风格的指针,解引用裸指针需要在unsafe中进行操作。
e.g.
fn main() {
let data1 = 5;
let mut data2 = 6;
let ptr1 = &data1 as *const i32;
let ptr2 = &mut data2 as *mut i32;
unsafe {
*ptr2 = 10;
}
println!("{}",data2);
}
裸指针可以指向 null,空指针解引用编译器也不会进行检查。
e.g.
fn main() {
let null_ptr : *mut i32 = std::ptr::null_mut();
unsafe {
*null_ptr = 100;
}
}