【Rust所有权】

什么是所有权

所有权是Rust最独特的特性,它让Rust无需GC就可以保证内存安全。Rust的核心特性就是所有权。

为什么需要所有权

要说清楚为什么需要所有权,就需要明白所有权你用来做什么的 -------内存管理;
所有程序在运行时都必须管理它们使用计算机内存的方式。

常见的内存管理方式

  1. 有些语言有垃圾收集机制,在程序运行时,它们会不断地寻找不再使用的内存。例如:Java、Go…
  2. 在其他语言中,程序员必须显示的分配和释放内存。C/C++

Rust所有权如何进行内存管理?

  1. 内存是通过一个所有权系统来管理的,其中包含一组编译器在编译时检查的规则;
  2. 对于某个值来说,当拥有它的变量走出作用范围时,内存会立即自动的交还给操作系统。 drop()

为什么要用所有权进行内存管理?

Rust为什么不选用上面的两种内存管理方式呢?
因为Rust定位是速度惊人且内存利用率极高的安全编程语言。使用垃圾收集器虽然能保证安全,但性能方面做不到极致,而程序员手动管理内存又存在很大的安全隐患。 所有权就很好的满足了上面两点。

所有权如何使用

1. 变量和数据

多个变量与同一个值的交互方式会根据值保存在stack还是heap上而有所不同

  • 值保存在stack上,下一个变量会复制上一个变量的值;
fn main() {
     let x = 6;
     let y = x;
     println!("x:{}, y:{}.", x, y);   
}
  • 值保存在heap中,赋值动作会将所有权移动到下一个值上
fn main() {
     let s1 = String::from("hello");
     let s2 = s1;
     println!("s1: {}, s2: {}.", s1, s2);   
}

▶Run
String的内存结构
复制栈上的值
让s1栈上的值失效

  • 当s1赋给s2,String的数据被复制了一份
    • 在stack上复制了一份指针、长度、容量
    • 并没有复制指针所指向的heap上的数据
  • 当变量离开作用域时,Rust会自动调用drop函数,并将变量使用的heap内存释放
  • 当s1、s2离开作用域时,他们都会尝试释放相同的内存,此时会导致二次释放。
  • 为了保证内存安全:
    • Rust没有尝试复制被分配的内存
    • Rust让s1失效,当s1离开作用域的时候,Rust不需要释放任何东西
  • 也许会将复制指针、长度、容量视为浅拷贝,但由于Rust让s1失效了,所以我们用一个新的术语:移动(Move)
  • 隐含一个设计原则:Rust不会自动创建数据的深拷贝。就运行时性能而言,任何自动赋值的操作都是廉价的

2.函数

在语义上,将值传递给函数和把值赋给变量是类似的:将值传递给函数将发生移动或复制

3.返回值

  • 函数在返回值的过程中同样也会发生所有权的转移
  • 当一个包含heap数据的变量离开作用域时,他的值就会被drop函数清理,除非数据的所有权移动到另一个变量上

如何使用某值而不获得其所有权

例如:
获得一个字符串的长度,你可能会用以下方式获取

fn main() {
     let s1 = String::from("hello");
     let (s2,len) = calculate_length(s1);
     println!("The length of '{}' is {}.", s2, len);   
}

fn calculate_length(s: String) -> (String, usize) {
    let length = s.len();
    (s,length)
}

▶Run

此时使用Rust的另一个特性“引用(Reference)”也许会更合适。

引用和借用

  • 引用的参数类型是&String而不是String
  • &符号就表示引用:允许你引用某些值而不取得其所有权
  • 我们把引用作为函数参数这个行为叫做借用
  • 我们是否可以修改借用的东西呢?默认情况下是不行的
  • 和变量一样,引用默认也是不可变的。加个mut就变为可变借用 &mut
    引用

可变借用

fn main(){
    let mut s = String::from("Hello");
    let s1 = &mut s;
    let s2 = &mut s;
    
    println!("The length of '{}' is {}.", s1, s2);
}
  • 可变引用有一个重要的限制:在特定作用域内,对某一块数据,只能有一个可变的引用
    • 这样做的好处是可以在编译时防止数据竞争
  • 下列三种行为下会发生数据竞争:
    • 两个或多个指针同时访问同一个数据
    • 至少有一个指针用于写入数据
    • 没有使用任何机制来同步对数据的访问
  • 可以通过创建新的作用域,来允许非同时的创建多个可变引用
  • 不可以同时拥有一个可变引用和一个不可变的引用
fn main(){
    let mut s = String::from("Hello");
    {
         let s2 = &mut s;   
    }
    let s1 = &mut s;
}

注意:Rust不允许出现悬垂引用(Dangling Reference)

  • 悬垂饮用(Dangling Reference): 一个指针引用了内存中的某个地址,而这块内存可能已经释放并分配给其他人使用了
  • 在Rust里,编译器可保证引用永远都不是悬垂引用:
    • 如果你引用了某些数据,编译器将保证在引用离开作用域之前数据不会离开作用域

下面是错误示范:

fn main(){
    let r = dangle();
}

fn dangle() -> &String {
    let s = String::from("hello");
    &s
}

▶Run

引用的规则

  • 在任何给定的时刻,只能满足下列条件之一:
    • 一个可变引用
    • 任意数量的不可变的引用
  • 引用必须一直有效

结束语

所有权作为Rust的核心之一,需要深刻的去理解它的设计思想。会对往后的Rust编程有很大帮助。
笔者也只刚刚开始学习Rust,不对的地方还请慷慨指出。也希望您可以留言交流心得~

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值