专栏
天翼云开发者社区

Rust入门(四) —— 理解所有权

2024-03-28 09:35:25 38阅读

从程序的内存管理说起

所有的程序都必须和计算机内存打交道,如何从内存中申请空间来存放程序的运行内容,如何在不需要的时候释放这些空间,成了重中之重,也是所有编程语言设计的难点之一。在计算机语言不断演变过程中,先后出现过两种主要的内存管理模式:手动内存管理(由程序员通过编码申请和释放内存,典型代表C,C++),通过垃圾回收机制实施的自动内存管理(典型代表Java,Golang)。

手动内存管理

在程序中,通过函数调用的方式来申请和释放内存。其特征在于:

(1)程序员可以控制内存分配、使用、释放的各个环节,控制粒度可以做到很细;另一方面,也要求程序员对内存分配是否成功、数据初始化、内存使用、数据共享、数据竞争、数据销毁、释放等做全面且细致的管控,否则就可能引入内存安全问题。

(2) C/C++语言支持隐式类型转换,指针运算,数组和指针转换,这为程序使用内存提供了巨大的灵活性;另一方面,也极易引入访问越界、类型错误(或数据溢出)、无效地址访问等内存安全问题。

总结而言,采用手动内存管理方式的编程语言,极度依赖程序员的编程经验,导致整体程序质量参差不齐。遗憾的是,那怕是经验丰富的程序员,也有考虑不周的情况,稍不留神就会引入内存安全问题。

垃圾回收机制(运行时GC)

垃圾回收机制(GC),在程序运行时不断寻找不再使用的内存,并在合适的时机将其释放。拥有垃圾回收机制的编程语言,在内存安全管理方面有巨大提升:大幅度降低了空指针、野指针、悬垂指针访问的异常情况,大幅度降低内存泄漏的风险。

但是,引入垃圾回收后,有引入了新的问题:

(1)性能开销:垃圾回收机制需要额外的系统资源和计算开销来进行垃圾收集,这可能会影响程序的性能,尤其是对于实时性要求较高的应用程序。

(2)不确定的回收时间:垃圾回收的时间是不确定的,并且垃圾回收可能会在程序运行时导致停顿(STOP THE WORLD),影响用户体验或者系统的实时性(在一些实时要求极高的应用中,这种不可预期的停顿是不可接受的)。

(3)系统资源占用变高或者出现波峰:由于垃圾回收机制通常需要维护一些附加数据结构来跟踪对象的引用关系,可能带来额外的内存开销;同时,在执行垃圾回收时,通常需要经过复杂的计算进行目标筛选,这可能出现CPU资源消耗飚高。

总结而言,采用垃圾回收机制的编程语言,在内存安全提升方面效果是显著的;但会引入一些系统资源、系统性能、系统响应即时性方面的消耗或开销,在一些特定场景中引入问题。

Rust的所有权机制

作为编程语言的后起之秀,Rust站在巨人(们)的肩膀上,审视着前人(们)走过的坎坎坷坷!GC这把重剑他拿起又放下......

多少次徘徊在图书馆里,希望从知识的海洋里找到新的方向和真理。突然某天发现,他寻求的真理就在图书馆的管理机制里(以图书借用类比内存管理)!

你看:

 

C/C++馆

借阅规则: 大家自由取阅,阅读后自行归还到书架相应的位置。

现状

(1) 大部分人自取、自阅、阅读完归还到书架指定位置;少部分人忘记归还,将书籍遗留在读书大厅,闭馆时由管理员统一整理(进程销毁回收)!

(2) A借阅后,给B传阅,而后传阅给C、D、E,最终由E还回 (共享)。

(3) A借阅后,B告诉A先不要还,一会他要看;结果B没有看、也没有还,这本书遗留在读书大厅(泄漏)!

(4) A借阅后,传阅 B,C,D, 随后D将书还回,A离开时准备去还书,却发现书找不到了,急得上蹦下串(重复释放)!

(5) A借阅后,与B共阅, A 三下五除二看完,然后将书归还,留下B一脸懵逼(悬垂访问)!

(6) A告诉B说有本精装《诗经》带插图和注解,非常适合陶冶情操,已经借出放在site108; B到位置上查阅,是一本《金瓶梅》 (野指针,脏数据)!

观感:过度自由,图书管理不善,经常失控。

 

Java/Golang馆

借阅规则: 大家自由取阅,在座位上阅读,离席后管理员收拾图书;离席保留图书请留下标签。

现状

(1)大家都是自取图书,找位置落座阅读,阅读完成后离开即可;

(2)图书馆有管理员会不定期巡逻,发现空座位上的图书会自动收走,放回书架;

(3)A借阅 book1, 而后去借book2, 为了保留book1,需要在座位上立一个暂留的牌子(引用保留);

(4)A借阅 book1,放置在site108;告诉B可以去查阅;B 到site108发现书已经被收走了(地址引用无效)!

(5)A正在专注阅读,管理员过来要求他站起来一下,以方便收取附近闲置的书籍(GC阻断)!

观感:图书管理妥善,只是来回穿梭劳作的管理员,破坏了这安静学习的氛围。

 

Rust琢磨,如果我管理这个图书馆,让每个人每次借一本图书并登记;阅读完毕后,举手示意,管理员进行登记注销,并回收图书。如此,图书借阅、归还不就变得井然有序了?

类比于此,在内存管理时,如果能够确保每次分配的内存,都由一个变量来唯一拥有;那么,当这个唯一拥有该内存的变量达到其生命周期的尽头时,这块内存就可以自动释放(并且必须释放)!如此这般,就可以实现内存安全管理、自动回收,并且不引入垃圾回收机制所带来的新问题

一试之下,果然灵应!

而这个机制,Rust将其称为所有权

 

理解Rust所有权机制

经过上一章节说明,我们明白了Rust引入所有权机制所解决的核心问题是:在无GC的前提下,可以做到自动内存回收! 即保证编程过程中内存安全,又不会引入传统GC机制所带来的负面效果。

接下来,我们将更加详细说明,Rust所有权机制的规则和使用。

所有权规则

进一步总结,Rust的所有权机制包括三条核心规则:

  1. Rust 中每一个值都被一个变量所拥有,该变量被称为值的所有者
  2. 一个值同时只能被一个变量所拥有,或者说一个值只能拥有一个所有者
  3. 当所有者(变量)离开作用域范围时,这个值将被丢弃(drop)

变量绑定

先看下面的例子:

fn var_bind_string() {
    let mut vstr = String::new();

    vstr.push_str("origin data");

    let mut nstr = vstr;
    nstr.push_str(" append something");

    println!("check the string values:: first ={}, updated={}", vstr, nstr);
}

示例中,创建了一个字符串变量(动态分配)vstr,并更新vstr的内容为 “origin data”;

而后,创建新变量nstr,并将vstr “赋值”给nstr,并更新nstr的内容,追加“append something”;

最后,尝试打印vstr,nstr的内容。

乍一看,这个代码没有什么问题;有编程经验的童鞋,可能还在考虑 vstr 和nstr 是否一致(nstr 和vstr指向同一块地址,内容一致?nstr基于vstr做了深度拷贝,是两块独立的内存地址?)......

然后,现实是,nstr和vstr 确实指向同一块内存地址,但二者并不能同时存在:

通过idel预编译提示,当我们将vstr “赋值”给nstr之后,nstr已经被“moved”,已经不可用了,当我们尝试访问它时就会报错!

下图展示变量的内存结构变化:

由此可见:

(1)当我们将一个包含内存分配的变量,“赋值”给另一个变量时,相应的内存所有权发生了转移,由此来保证“一个值只能被一个变量所拥有”;

(2) rust中“=”的含义和行为相比于一般编程语言是有所区别的;在rust中,这个过程不再使用“赋值”这个术语,而使用“变量绑定”这个术语,更贴切的表述了内存所有权发生转移的行为;

(3)特别需要注意的是,变量绑定不仅发生在块逻辑中,也发生在函数调用过程中;因此,在函数调用时,需要谨慎处理入参和返回,需要注意是否发生了所有权移动;另外,函数传递时,根据需要,可以通过引用传递,来避免所有权转移。

 

堆变量和栈变量

栈和堆是编程语言最核心的数据结构,但是在很多语言中,你并不需要深入了解栈与堆(例如golang,你并不需要了解变量是在堆上分配还是在栈上分配,因为golang会自动转换)。 但对于 Rust 这样的系统编程语言,值是位于栈上还是堆上非常重要, 因为这会影响程序的行为和性能。

如果读者对堆和栈相关的基础知识不够牢固,小编强烈建议您去补习一下相关知识,这对您了解rust相关机制大有裨益!

Rust的变量绑定在处理堆变量和栈变量时,也是不一样的,如下图:

由图可见:

(1) b=a; 变量绑定后,a依然可用;

(2) 而 str_d= str_c; 变量绑定后,str_c就不可用了。

这是因为 a,b都是栈变量,rust在处理栈变量“赋值”时,采用了copy方式(由于栈变量通常很小,copy数据量不大;而且栈复制效率也高,所有rust选择通过clone的方式处理栈变量的绑定)。而String类型是堆变量,所以需要通过所有权转移来满足内存值的所有权规则,使得系统能自动管理内存分配和回收!

因此,可以理解为rust的所有权规则其实就是针对堆分配的内存管理!

另外补充一点,rust在函数传递时,是值传递。这就可以解释,为何堆变量在传递到函数后,所有权发生了转移(值传递的函数调用,入参s 传入后,在内部生成了 一个同名的局部变量s',函数体内用的其实是这个局部变量;等效于,函数调用时 发生 s'=s的变量绑定)!

引用与借用

前两章节,我们详细解释了Rust的所有权机制的原理和实现,阐述了所有权机制如何使Rust在没有垃圾回收机制的前提下,实现了自动化内存管理,从而提高了内存安全。然而,如果仅仅支持通过转移所有权的方式获取一个值,那会让程序变得复杂(就像上面示例的打印字符串函数一样,只是调用了一个打印,字符串变量就变得不可用了)。 Rust 能否像其它编程语言一样,使用某个变量的指针或者引用呢?答案是可以。

Rust 也支持引用,是一个指向变量的地址,与其他语言(C,Golang)含义一致。

Rust 通过 借用(Borrowing) 这个概念来达成上述的目的,获取变量的引用,称之为借用(borrowing)。正如现实生活中,如果一个人拥有某样东西,你可以从他那里借来,当使用完毕后,也必须要物归原主。

改下print_stringx函数如下:

函数入参是一个字符串引用(而不是字符串实例),这样函数调用就不会发生所有权转移。在rust中,这是常用的方式!

如上图,pstr就是对vstr 的借用(不可变借用),此时pstr不拥有内存数据,但可以共享vstr的值。

可变引用

上面的例子我们可以看见,通过借用,我们可以共享变量的值。但这似乎还不够,因为我们需要一些函数和接口来对数据进行加工(修改)。我们期望有这样的代码:

fn append_string(dst: &String, data: &str) {
    dst.push_str(data);
}

但上面的代码编译时就会报错:无法将dst引用,借用为可写的借用!建议将dst引用改变为可写引用(Rust的编译器是真的保姆级编译器)。

调整如下

fn append_string(dst: &mut String, data: &str) {
    dst.push_str(data);
}

fn test_modify_string() {
    let mut vstr = String::from("data from heap");  // 注意,可变引用成立的前提是变量自身必须申明为可变
    append_string(&mut vstr, "something to append");
    println!("{}", vstr)
}

多个借用间的竞态检查

见下的例子:

fn append_string(dst: &mut String, data: &str) {
    dst.push_str(data);
}

fn test_mutilp_borrow() {
    let mut vstr = String::from("data from heap");
    let pstr = &vstr;

    print_stringx(&pstr);
    let mstr = &mut vstr;
    append_string(mstr, "append");
    println!("{} {}", pstr, mstr);
}

测试程序尝试对vstr进行:1打印初始值,2,修改内容;3,打印修改后的值。

见截图,程序编译时报错:尝试将vstr 进行可变借用到mstr时异常,原因是vstr在这之前已经被pstr进行了不可变借用!

Rust在编译时会进行数据竞态检查,确保流程中没有明显的数据竞争。例子中,pstr对vstr进行了不可变借用,其含义为在pstr的生命周期内,期望vstr保持不变,以保证pstr对vstr数据引用的一致性;但是mstr尝试对vstr进行可变借用,那么意味着其生命周期内可能对vstr进行修改,并且mstr的生命周期与pstr的生命周期存在重叠;基于此,Rust编译检查器,可以判定mstr的可变借用会破坏pstr的数据一致性要求,因而被禁止!

Rust对于借用是数据竞态的检查要求为:

(1)确保不存在多个可变借用同时对数据进行同时修改的情况;即,同一时间,最多只能存在一个可变借用。

(2)当有不可变借用存在的时候,不允许进行可变借用;反之依然。

(3)可以同时存在多个不可变借用。

(4)借用期间,引用必须总是有效的(在)。

 

关于Rust如何保障在借用期间,引用必须总是有效,我们将开一个新的主题——rust的生命周期 进行详细说明。

那就下回分解了老铁!

  • 3
  • 2
  • 0
0 评论
0/1000
评论(0) 发表评论
谭****勇

谭****勇

13 篇文章 1 粉丝
关注

Rust入门(四) —— 理解所有权

2024-03-28 09:35:25 38阅读

从程序的内存管理说起

所有的程序都必须和计算机内存打交道,如何从内存中申请空间来存放程序的运行内容,如何在不需要的时候释放这些空间,成了重中之重,也是所有编程语言设计的难点之一。在计算机语言不断演变过程中,先后出现过两种主要的内存管理模式:手动内存管理(由程序员通过编码申请和释放内存,典型代表C,C++),通过垃圾回收机制实施的自动内存管理(典型代表Java,Golang)。

手动内存管理

在程序中,通过函数调用的方式来申请和释放内存。其特征在于:

(1)程序员可以控制内存分配、使用、释放的各个环节,控制粒度可以做到很细;另一方面,也要求程序员对内存分配是否成功、数据初始化、内存使用、数据共享、数据竞争、数据销毁、释放等做全面且细致的管控,否则就可能引入内存安全问题。

(2) C/C++语言支持隐式类型转换,指针运算,数组和指针转换,这为程序使用内存提供了巨大的灵活性;另一方面,也极易引入访问越界、类型错误(或数据溢出)、无效地址访问等内存安全问题。

总结而言,采用手动内存管理方式的编程语言,极度依赖程序员的编程经验,导致整体程序质量参差不齐。遗憾的是,那怕是经验丰富的程序员,也有考虑不周的情况,稍不留神就会引入内存安全问题。

垃圾回收机制(运行时GC)

垃圾回收机制(GC),在程序运行时不断寻找不再使用的内存,并在合适的时机将其释放。拥有垃圾回收机制的编程语言,在内存安全管理方面有巨大提升:大幅度降低了空指针、野指针、悬垂指针访问的异常情况,大幅度降低内存泄漏的风险。

但是,引入垃圾回收后,有引入了新的问题:

(1)性能开销:垃圾回收机制需要额外的系统资源和计算开销来进行垃圾收集,这可能会影响程序的性能,尤其是对于实时性要求较高的应用程序。

(2)不确定的回收时间:垃圾回收的时间是不确定的,并且垃圾回收可能会在程序运行时导致停顿(STOP THE WORLD),影响用户体验或者系统的实时性(在一些实时要求极高的应用中,这种不可预期的停顿是不可接受的)。

(3)系统资源占用变高或者出现波峰:由于垃圾回收机制通常需要维护一些附加数据结构来跟踪对象的引用关系,可能带来额外的内存开销;同时,在执行垃圾回收时,通常需要经过复杂的计算进行目标筛选,这可能出现CPU资源消耗飚高。

总结而言,采用垃圾回收机制的编程语言,在内存安全提升方面效果是显著的;但会引入一些系统资源、系统性能、系统响应即时性方面的消耗或开销,在一些特定场景中引入问题。

Rust的所有权机制

作为编程语言的后起之秀,Rust站在巨人(们)的肩膀上,审视着前人(们)走过的坎坎坷坷!GC这把重剑他拿起又放下......

多少次徘徊在图书馆里,希望从知识的海洋里找到新的方向和真理。突然某天发现,他寻求的真理就在图书馆的管理机制里(以图书借用类比内存管理)!

你看:

 

C/C++馆

借阅规则: 大家自由取阅,阅读后自行归还到书架相应的位置。

现状

(1) 大部分人自取、自阅、阅读完归还到书架指定位置;少部分人忘记归还,将书籍遗留在读书大厅,闭馆时由管理员统一整理(进程销毁回收)!

(2) A借阅后,给B传阅,而后传阅给C、D、E,最终由E还回 (共享)。

(3) A借阅后,B告诉A先不要还,一会他要看;结果B没有看、也没有还,这本书遗留在读书大厅(泄漏)!

(4) A借阅后,传阅 B,C,D, 随后D将书还回,A离开时准备去还书,却发现书找不到了,急得上蹦下串(重复释放)!

(5) A借阅后,与B共阅, A 三下五除二看完,然后将书归还,留下B一脸懵逼(悬垂访问)!

(6) A告诉B说有本精装《诗经》带插图和注解,非常适合陶冶情操,已经借出放在site108; B到位置上查阅,是一本《金瓶梅》 (野指针,脏数据)!

观感:过度自由,图书管理不善,经常失控。

 

Java/Golang馆

借阅规则: 大家自由取阅,在座位上阅读,离席后管理员收拾图书;离席保留图书请留下标签。

现状

(1)大家都是自取图书,找位置落座阅读,阅读完成后离开即可;

(2)图书馆有管理员会不定期巡逻,发现空座位上的图书会自动收走,放回书架;

(3)A借阅 book1, 而后去借book2, 为了保留book1,需要在座位上立一个暂留的牌子(引用保留);

(4)A借阅 book1,放置在site108;告诉B可以去查阅;B 到site108发现书已经被收走了(地址引用无效)!

(5)A正在专注阅读,管理员过来要求他站起来一下,以方便收取附近闲置的书籍(GC阻断)!

观感:图书管理妥善,只是来回穿梭劳作的管理员,破坏了这安静学习的氛围。

 

Rust琢磨,如果我管理这个图书馆,让每个人每次借一本图书并登记;阅读完毕后,举手示意,管理员进行登记注销,并回收图书。如此,图书借阅、归还不就变得井然有序了?

类比于此,在内存管理时,如果能够确保每次分配的内存,都由一个变量来唯一拥有;那么,当这个唯一拥有该内存的变量达到其生命周期的尽头时,这块内存就可以自动释放(并且必须释放)!如此这般,就可以实现内存安全管理、自动回收,并且不引入垃圾回收机制所带来的新问题

一试之下,果然灵应!

而这个机制,Rust将其称为所有权

 

理解Rust所有权机制

经过上一章节说明,我们明白了Rust引入所有权机制所解决的核心问题是:在无GC的前提下,可以做到自动内存回收! 即保证编程过程中内存安全,又不会引入传统GC机制所带来的负面效果。

接下来,我们将更加详细说明,Rust所有权机制的规则和使用。

所有权规则

进一步总结,Rust的所有权机制包括三条核心规则:

  1. Rust 中每一个值都被一个变量所拥有,该变量被称为值的所有者
  2. 一个值同时只能被一个变量所拥有,或者说一个值只能拥有一个所有者
  3. 当所有者(变量)离开作用域范围时,这个值将被丢弃(drop)

变量绑定

先看下面的例子:

fn var_bind_string() {
    let mut vstr = String::new();

    vstr.push_str("origin data");

    let mut nstr = vstr;
    nstr.push_str(" append something");

    println!("check the string values:: first ={}, updated={}", vstr, nstr);
}

示例中,创建了一个字符串变量(动态分配)vstr,并更新vstr的内容为 “origin data”;

而后,创建新变量nstr,并将vstr “赋值”给nstr,并更新nstr的内容,追加“append something”;

最后,尝试打印vstr,nstr的内容。

乍一看,这个代码没有什么问题;有编程经验的童鞋,可能还在考虑 vstr 和nstr 是否一致(nstr 和vstr指向同一块地址,内容一致?nstr基于vstr做了深度拷贝,是两块独立的内存地址?)......

然后,现实是,nstr和vstr 确实指向同一块内存地址,但二者并不能同时存在:

通过idel预编译提示,当我们将vstr “赋值”给nstr之后,nstr已经被“moved”,已经不可用了,当我们尝试访问它时就会报错!

下图展示变量的内存结构变化:

由此可见:

(1)当我们将一个包含内存分配的变量,“赋值”给另一个变量时,相应的内存所有权发生了转移,由此来保证“一个值只能被一个变量所拥有”;

(2) rust中“=”的含义和行为相比于一般编程语言是有所区别的;在rust中,这个过程不再使用“赋值”这个术语,而使用“变量绑定”这个术语,更贴切的表述了内存所有权发生转移的行为;

(3)特别需要注意的是,变量绑定不仅发生在块逻辑中,也发生在函数调用过程中;因此,在函数调用时,需要谨慎处理入参和返回,需要注意是否发生了所有权移动;另外,函数传递时,根据需要,可以通过引用传递,来避免所有权转移。

 

堆变量和栈变量

栈和堆是编程语言最核心的数据结构,但是在很多语言中,你并不需要深入了解栈与堆(例如golang,你并不需要了解变量是在堆上分配还是在栈上分配,因为golang会自动转换)。 但对于 Rust 这样的系统编程语言,值是位于栈上还是堆上非常重要, 因为这会影响程序的行为和性能。

如果读者对堆和栈相关的基础知识不够牢固,小编强烈建议您去补习一下相关知识,这对您了解rust相关机制大有裨益!

Rust的变量绑定在处理堆变量和栈变量时,也是不一样的,如下图:

由图可见:

(1) b=a; 变量绑定后,a依然可用;

(2) 而 str_d= str_c; 变量绑定后,str_c就不可用了。

这是因为 a,b都是栈变量,rust在处理栈变量“赋值”时,采用了copy方式(由于栈变量通常很小,copy数据量不大;而且栈复制效率也高,所有rust选择通过clone的方式处理栈变量的绑定)。而String类型是堆变量,所以需要通过所有权转移来满足内存值的所有权规则,使得系统能自动管理内存分配和回收!

因此,可以理解为rust的所有权规则其实就是针对堆分配的内存管理!

另外补充一点,rust在函数传递时,是值传递。这就可以解释,为何堆变量在传递到函数后,所有权发生了转移(值传递的函数调用,入参s 传入后,在内部生成了 一个同名的局部变量s',函数体内用的其实是这个局部变量;等效于,函数调用时 发生 s'=s的变量绑定)!

引用与借用

前两章节,我们详细解释了Rust的所有权机制的原理和实现,阐述了所有权机制如何使Rust在没有垃圾回收机制的前提下,实现了自动化内存管理,从而提高了内存安全。然而,如果仅仅支持通过转移所有权的方式获取一个值,那会让程序变得复杂(就像上面示例的打印字符串函数一样,只是调用了一个打印,字符串变量就变得不可用了)。 Rust 能否像其它编程语言一样,使用某个变量的指针或者引用呢?答案是可以。

Rust 也支持引用,是一个指向变量的地址,与其他语言(C,Golang)含义一致。

Rust 通过 借用(Borrowing) 这个概念来达成上述的目的,获取变量的引用,称之为借用(borrowing)。正如现实生活中,如果一个人拥有某样东西,你可以从他那里借来,当使用完毕后,也必须要物归原主。

改下print_stringx函数如下:

函数入参是一个字符串引用(而不是字符串实例),这样函数调用就不会发生所有权转移。在rust中,这是常用的方式!

如上图,pstr就是对vstr 的借用(不可变借用),此时pstr不拥有内存数据,但可以共享vstr的值。

可变引用

上面的例子我们可以看见,通过借用,我们可以共享变量的值。但这似乎还不够,因为我们需要一些函数和接口来对数据进行加工(修改)。我们期望有这样的代码:

fn append_string(dst: &String, data: &str) {
    dst.push_str(data);
}

但上面的代码编译时就会报错:无法将dst引用,借用为可写的借用!建议将dst引用改变为可写引用(Rust的编译器是真的保姆级编译器)。

调整如下

fn append_string(dst: &mut String, data: &str) {
    dst.push_str(data);
}

fn test_modify_string() {
    let mut vstr = String::from("data from heap");  // 注意,可变引用成立的前提是变量自身必须申明为可变
    append_string(&mut vstr, "something to append");
    println!("{}", vstr)
}

多个借用间的竞态检查

见下的例子:

fn append_string(dst: &mut String, data: &str) {
    dst.push_str(data);
}

fn test_mutilp_borrow() {
    let mut vstr = String::from("data from heap");
    let pstr = &vstr;

    print_stringx(&pstr);
    let mstr = &mut vstr;
    append_string(mstr, "append");
    println!("{} {}", pstr, mstr);
}

测试程序尝试对vstr进行:1打印初始值,2,修改内容;3,打印修改后的值。

见截图,程序编译时报错:尝试将vstr 进行可变借用到mstr时异常,原因是vstr在这之前已经被pstr进行了不可变借用!

Rust在编译时会进行数据竞态检查,确保流程中没有明显的数据竞争。例子中,pstr对vstr进行了不可变借用,其含义为在pstr的生命周期内,期望vstr保持不变,以保证pstr对vstr数据引用的一致性;但是mstr尝试对vstr进行可变借用,那么意味着其生命周期内可能对vstr进行修改,并且mstr的生命周期与pstr的生命周期存在重叠;基于此,Rust编译检查器,可以判定mstr的可变借用会破坏pstr的数据一致性要求,因而被禁止!

Rust对于借用是数据竞态的检查要求为:

(1)确保不存在多个可变借用同时对数据进行同时修改的情况;即,同一时间,最多只能存在一个可变借用。

(2)当有不可变借用存在的时候,不允许进行可变借用;反之依然。

(3)可以同时存在多个不可变借用。

(4)借用期间,引用必须总是有效的(在)。

 

关于Rust如何保障在借用期间,引用必须总是有效,我们将开一个新的主题——rust的生命周期 进行详细说明。

那就下回分解了老铁!

文章来自专栏

后台开发技术分享

13 篇文章 3 订阅
0 评论
0/1000
评论(0) 发表评论
  • 3
    点赞
  • 2
    收藏
  • 0
    评论