rust引用和借用的使用小结

 更新时间:2023年01月04日 11:31:01   作者:zy010101  
在rust中,引用的语法非常简单。通过&来取引用,通过*来解引用,这篇文章主要介绍了rust引用和借用的使用小结,总的来说,借用规则,同一时刻,你只能拥有要么一个可变引用, 要么任意多个不可变引用,具体内容详情跟随小编一起看看吧

引用和借用

如果每次都发生所有权的转移,程序的编写就会变得异常复杂。因此rust和其它编程语言类似,提供了引用的方式来操作。获取变量的引用,称为借用。类似于你借别人的东西来使用,但是这个东西的所有者不是你。引用不会发生所有权的转移

引用的使用

在rust中,引用的语法非常简单。通过&来取引用,通过*来解引用。例如:

fn main() {
    let s1: String = "Hello".to_string();
    let s2: &String = &s1;       // s2引用s1

    println!("{s1}");
    println!("{s2}");
}

这段代码可以正常运行,因为s2引用的s1,不会发生所有权的转移。再来看一个例子,通过引用来传递函数参数。

fn main() {
    let s = "Hello".to_string();
    let len = calculate_length(&s);     // 引用

    println!("{s}");
    println!("{len}");
}


fn calculate_length(s: &String) -> usize {
    s.len()
}   

在calculate_length中,s是一个引用,它不具备所有权,因此在函数调用结束的时候,s的作用域虽然结束了,但是不会调用drop。

可变引用与不可变引用

在刚才的例子中,只是获取了字符串的长度,相当于我们读取了变量。在rust中,引用默认也是不可变的,如果需要通过引用修改变量,那么必须使用可变引用。可变引用和可变变量一样,都是通过关键字mut来实现的。例如:

fn main() {
    let mut s = String::from("hello");
    change(&mut s);     // 可变引用
    println!("{s}");
}

fn change(some_string: &mut String) {
    some_string.push_str(", world");
}

这段代码输出hello, world,可见我们通过可变引用修改了s的值,但是在这个过程中并没有涉及所有权的转移。

事实上,事情并没有这么简单。可变引用并不是可以随心所欲的被使用。它有一个很大的限制,“同一作用域,一个变量只能有一个可变引用”。例如:

fn main() {
    let mut s = String::from("hello");

    let r1 = &mut s;
    let r2 = &mut s;     // 同一作用域,无法创建两个可变引用。

    println!("{}, {}", r1, r2);
}

两个可变引用,可能会出现“同时写入”这种情况,导致内存不安全的情形发生。如果在不同的作用域,可以有多个可变引用,但是它们不能同时被拥有。例如:

fn main() {
    let mut s = String::from("hello");

    {
        let r1 = &mut s;
        println!("{r1}");
    } // r1 在这里离开了作用域,所以我们完全可以创建一个新的引用

    let r2 = &mut s;
    println!("{r2}");
}

同时rust也不允许同时存在可变引用和不可变引用。因为不可变引用可能会因可变引用变得失效。下面以一段C++代码来说明这一点。

#include<vector>
#include<string>
#include<iostream>

using namespace std;

int main() {
    
    // 可读引用因可变引用而变得失效
    vector<string> vs;
    vs.push_back("hello");

    auto & elem = vs[0];

    vs.push_back("world");      // push_back会导致vs指向的整段内存被重新分配并移到了另一个地址,原本迭代器里面的引用就全部变成悬垂指针了。

    cout << vs[0] << endl;
    cout << elem << endl;       // 试图使用悬垂指针
    
    return 0;
}

这段代码执行之后,结果如下所示:

hello
Segmentation fault (core dumped)

很明显,这里的段错误正是由于试图使用悬垂指针引起的。而rust特殊的可变引用和不可变引用机制避免了这种错误的发生。例如:

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

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

    &s
}   // 返回s的引用,函数结束,s移出作用域,调用drop函数清理内存,那么返回的引用将会变成悬垂引用,从而引发错误。

这段rust代码无法编译通过,从而避免了像上面C++代码那样的运行时错误。

正如Rust 程序设计语言中所言

这一限制以一种非常小心谨慎的方式允许可变性,防止同一时间对同一数据存在多个可变引用。新 Rustacean 们经常难以适应这一点,因为大部分语言中变量任何时候都是可变的。这个限制的好处是 Rust 可以在编译时就避免数据竞争。数据竞争(data race)类似于竞态条件,它可由这三个行为造成:

两个或更多指针同时访问同一数据。
至少有一个指针被用来写入数据。
没有同步数据访问的机制。

Rust 的编译器一直在优化,早期的时候,引用的作用域跟变量作用域是一致的,这对日常使用带来了很大的困扰,你必须非常小心的去安排可变、不可变变量的借用,免得无法通过编译,例如以下代码:

fn main() {
   let mut s = String::from("hello");

    let r1 = &s;
    let r2 = &s;
    println!("{} and {}", r1, r2);
    // 新编译器中,r1,r2作用域在这里结束

    let r3 = &mut s;
    println!("{}", r3);
} // 老编译器中,r1、r2、r3作用域在这里结束
  // 新编译器中,r3作用域在这里结束

在老版本的编译器中(Rust 1.31 前),将会报错,因为 r1 和 r2 的作用域在花括号 } 处结束,那么 r3 的借用就会触发 无法同时借用可变和不可变的规则。但是在新的编译器中,该代码将顺利通过,因为 引用作用域的结束位置从花括号变成最后一次使用的位置,因此 r1 借用和 r2 借用在 println! 后,就结束了,此时 r3 可以顺利借用到可变引用。

NLL

对于这种编译器优化行为,Rust 专门起了一个名字 —— Non-Lexical Lifetimes(NLL),专门用于找到某个引用在作用域(})结束前就不再被使用的代码位置。

总结

  • 总的来说,借用规则如下:
  • 同一时刻,你只能拥有要么一个可变引用, 要么任意多个不可变引用引用必须总是有效的 参考资料

Rust 程序设计语言
Rust单线程下为什么还是只能有一个可变引用呢?
Rust语言圣经

到此这篇关于rust引用和借用的文章就介绍到这了,更多相关rust引用和借用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • rust zip异步压缩与解压的代码详解

    rust zip异步压缩与解压的代码详解

    在使用actix-web框架的时候,如果使用zip解压任务将会占用一个工作线程,因为zip库是同步阻塞的,想用异步非阻塞需要用另一个库,下面介绍下rust zip异步压缩与解压的示例,感兴趣的朋友一起看看吧
    2024-04-04
  • Rust动态调用字符串定义的Rhai函数方式

    Rust动态调用字符串定义的Rhai函数方式

    Rust中使用Rhai动态调用字符串定义的函数,通过eval_expression_with_scope实现,但参数传递和函数名处理有局限性,使用FnCall功能更健壮,但更复杂,总结提供了更通用的方法,但需要处理更多错误情况
    2025-02-02
  • 如何使用Rust的向量存储值列表

    如何使用Rust的向量存储值列表

    本文介绍了在Rust中使用向量存储值列表的方法,包括创建、更新、读取、遍历、存储多种类型以及内存释放等方面,向量是Rust中常用且强大的集合类型,熟练掌握其用法有助于编写高效且安全的代码
    2025-02-02
  • 一文掌握Rust编程中的生命周期

    一文掌握Rust编程中的生命周期

    在Rust语言中, 每一个引用都有其生命周期, 通俗讲就是每个引用在程序执行的过程中都有其自身的作用域, 一旦离开其作用域, 其生命周期也宣告结束, 值不再有效,这篇文章主要介绍了Rust编程中的生命周期,需要的朋友可以参考下
    2023-11-11
  • Rust日期与时间的操作方法

    Rust日期与时间的操作方法

    Rust的时间操作主要用到chrono库,接下来我将简单选一些常用的操作进行介绍,感兴趣的朋友跟随小编一起看看吧
    2023-09-09
  • Rust数据类型之结构体Struct的使用

    Rust数据类型之结构体Struct的使用

    结构体是Rust中非常强大和灵活的数据结构,可以用于组织和操作各种类型的数据,本文就来介绍一下Rust数据类型之结构体Struct的使用,感兴趣的可以了解一下
    2023-12-12
  • 浅析Rust多线程中如何安全的使用变量

    浅析Rust多线程中如何安全的使用变量

    这篇文章主要为大家详细介绍了Rust如何在线程的闭包中安全的使用变量,包括共享变量和修改变量,文中的示例代码讲解详细,有需要的小伙伴可以参考下
    2025-01-01
  • Rust之Substrate框架中Core详解

    Rust之Substrate框架中Core详解

    Substrate是一个用于构建区块链的开发框架,它由Parity团队基于Rust语言开发而成,是一个开箱即用的区块链构造器,本文详细介绍了Substrate框架中的Core,需要的朋友可以参考下
    2023-05-05
  • Rust读取配置文件的实现

    Rust读取配置文件的实现

    本文主要介绍了Rust读取配置文件的实现,主要读取Cargo.toml文件,读取.env文件和读取自定义toml文件这三种,具有一定的参考价值,感兴趣的可以了解一下
    2024-03-03
  • Rust实现grep命令行工具的方法

    Rust实现grep命令行工具的方法

    这篇文章主要介绍了Rust实现grep命令行工具的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-07-07

最新评论