1.6 所有权解决了什么问题?及其使用方法


上一节我们快速浏览了函数及控制结构,加上1.4节的数据类型,是不是跃跃欲试,想大干一场了呢?哈哈,脚印还得一个一个踩,我们今天还得说一说rust的一个重要概念-- 所有权。

我们先不讲所有权概念,先来看一个例子,有什么问题,再看看主流语言都是怎么解决这些问题的,有什么痛点,再来看rust的解决方法,就水到渠成,能更好地理解了。

考虑一个查找函数:(力扣高手勿喷 )给一个数组和一个目标值,用循环遍历数组找出等于目标值的元素索引。

fn main() {
    let data = vec![10, 42, 9, 8];
    let v = 42;
    if let Some(pos) = find_pos(data, v) {
        println!("Found {} at {}", v, pos);
    }
}

fn find_pos(data: Vec, v: u32) -> Option {
    for (pos, item) in data.iter().enumerate() {
        if *item == v {
            return Some(pos);
        }
    }
    None
}

在C++中,find_pos是传了一个数组的引用,所以堆上数组还是只有一份,只是引用计数+1了(浅拷贝),而多个调用就会出现多个引用,这些引用是通过专门维护引用的代码来管理的,运行时会带来一定的性能损耗。

在java中,GC是通过追踪式定时查看哪些内存标记不使用了,就会清理掉,同样也会带来运行时性能损耗。

我们可以看出,主流语言都会有一些问题。那rust有什么好方法吗?

有,rust借鉴了C++的move语义,配合使用所有权,高效、优美地解决了以上问题,下面我们来看rust是怎么做到的:

fn main() {
    let data = vec![10, 42, 9, 8];    //data是数组的所有者
    let v = 42;
    if let Some(pos) = find_pos(data, v) {    //data在函数调用时,把所有权移动给函数data`,data自动失去数组所有权,编译器会阻止后续代码访问data
        println!("Found {} at {}", v, pos);
    }
}

fn find_pos(data: Vec, v: u32) -> Option {    //函数作用域开始,data`拥有数组所有权
    for (pos, item) in data.iter().enumerate() {
        if *item == v {
            return Some(pos);
        }
    }
    None
}//    函数作用域结束,data`被丢弃,堆内存被释放

我加了注释,你们应该很容易能看明白,rust通过move移动语义把数组所有权交给了函数参数,在函数里data`拥有数组的所有权,而函数结束时数组也被安全释放。

这里没有使用多个引用,也不用在运行时再去管理引用计数或者定时清理标记未使用内存块,而是在编译期就安全的管理了堆内存。这就是rust优秀的内存管理之一,优雅、高效。只要能编译通过,rust程序一般而言都不会有什么问题,所以我们很多时候都是在和编译器作斗真

来看看rust所有权三大原则:

  1. 一个值只能被一个变量拥有,这个变量是这个值的所有者。
  2. 一个值在同一时刻只能有一个所有者,所以在函数传参、变量赋值、函数返回等行为,都是旧所有者把所有权移动给新所有者,以符合这一规则。
  3. 当所有者离开其作用域,他拥有的值被丢弃,内存安全释放。

所有权虽然很好的解决了多重引用、引用计数管理以及运行时定时检查内存的问题,但是也规定了一个时刻只能有一个变量拥有值,那像上个例子,若我们程序功能要求在函数传参后,需要继续使用原data,还想要使用它指向的数组呢?

这就要用到clone()函数,她可以把数组在内存拷贝一份,然后传回新数组地址给函数调用(深拷贝):

fn main() {
    let data = vec![10, 42, 9, 8];
    let v = 42;
    if let Some(pos) = find_pos(data.clone()), v) {    //传值给函数的是clone()的新数组,原data可继续使用,编译器不会报错
        println!("Found {} at {}", v, pos);
    }
}

fn find_pos(data: Vec, v: u32) -> Option {    //clone的数组所有权在data`
    for (pos, item) in data.iter().enumerate() {
        if *item == v {
            return Some(pos);
        }
    }
    None
}    //data`离开作用域,clone的数组失效,堆内存被正确释放

当然,如果有需求需要多次clone(),这样深拷贝多个堆数据也不高效,还会用到下一节要说到的copy和borrow等语法。

好了,我们学习了rust很重要的所有权概念,知道了他解决了什么问题,为什么要这么设计,还对比了rust和主流语言的内存管理机制,相信你已经更好理解了所有权,我觉得写rust程序越写越能养成好习惯,因为编译器会不停地督促我写出更健壮的代码 对于今天的内容你有什么看法呢?欢迎在评论区留言讨论!如果觉得文章有用,记得点赞关注加收藏,以后就会第一时间收到文章推送啦 :D

本文由ByteBunny原创,欢迎关注,带你一起长知识!

展开阅读全文

页面更新:2024-04-10

标签:所有权   目标值   高效   编译器   数组   所有者   变量   使用方法   函数   内存   作用

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号

Top