rust 引用怎么用

这篇具有很好参考价值的文章主要介绍了rust 引用怎么用。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

文章会通过三个例子来了解 rust 所有权的机制,都是从应用的角度来说明,rust 所有权底层是如何实现会在后续的内容中介绍。

理解RUST所有权规则:

  • RUST中的每个值都有一个对应的变量作为它的所有者
  • 同一时间内,值有且仅有一个所有者
  • 当所有者离开自己的作用域时,它持有的值就会被释放掉

基础篇

下面的示例代码编译不通过,在 s1 赋值给变量 s2 的过程中,字符串 neojos 值的所有权由 s1 转移给了 s2,然后 s1 变成了未初始化的状态。

fn main() {
   let s1: String = "neojos".to_string();
   let s2 = s1;
   println!("print s1:{}", s1)
}

假设是 go 语言来实现同样的代码,程序没有任何问题,s1 和 s2 实际指向的同样的字符串,可以正常打印 s1,赋值前后压根不会对 s1 产生影响。而 rust 偏偏要引入所有权的概念,内存中的一个值只能属于一个变量,最终,字符串值属于了 s2 之后,s1 也就相当于被垃圾回收了。

快速解决上述问题的方案,是将赋值的方式修改为引用赋值,就像下面这样,给 s2 赋值的是 s1 的引用,这样 s2 就不能接管 neojos 字符串的所有权,引用赋值过程就属于 借用。下面的方式属于只读借用,最终读取的还是 s1 。

还可以是 &mut 的可写引用,如果要想修改指针的值,就必须声明为可写引用。

fn main() {
   let s1: String = "neojos".to_string();
   let s2 = &s1;
   println!("print s1:{}", s1)
}

如果我们取引用的值,会发生值的移动吗?比如下面的例子,s2 指向了 s1 的可修改引用,s3 试图通过从 s2 取值,那么,s1 变量所拥有的值会发生转移吗?

fn main() {
   let mut s1: String = "neojos".to_string();
   let s2 = &mut s1;
   let s3: String = *s2;
   println!("print s1:{}", s1);
}

代码编译会发生报错,报错的信息见截图。看 move occurs 的提示,感觉 *s2 发生了所有权转移,但又补充没有实现 Copy 特性。既然 *s2 属于值类型,就应该发生所有权转移,但提示没有实现 Copy 特性,赋值语句怎么变成了 Copy 特性了呢?只有基本类型在赋值的过程中,才会发生值拷贝,String 类型的赋值不应该是值拷贝。

rust 引用怎么用,rust 编程开发,rust,开发语言,后端

从类型上推断,s1 的类型是 mut String,s2 的类型是 &mut String,s3 的类型是 String。 然后从多个角度思考这个问题:

① rust 中 &mut 是排它操作,在 s2 的生命期内不应该有其它值能重新引用到 s1,s3 试图在转移 s1;
② 有没有什么途径可以使这个例子正常编译?

丈二和尚摸不着头脑,我们继续简化一下上面的例子。按照常规的处理思路先来验证一番,通过编译器的提示来了解 rust 的处理思路。编译器给我们提供了“暗示”的能力,帮我们直观推测出变量的类型。

fn main() {
    let mut s1: String = "neojos".to_string();
    let s2 = &mut s1;
    s1 = "change name".to_string();
    print!("{}", s2)
}

编译器的类型暗示能力:
rust 引用怎么用,rust 编程开发,rust,开发语言,后端

在给 s1 重新赋值的过程中,编译器报错:cannot assign to s1 because it is borrowed,s2 借用了变量 s1,在 s2 变量没有被回收之前,rust 限制不能对 s1 进行操作。在控制台执行 rustc --explain E0506 查看官方的解释

在 s2 引用没有释放之前,s1 不能被赋予一个新值。这样的限制保证了 s2 引用的值不会发生改变,但 rust 底层是怎么实现这样的限制的?这样的处理操作有点类似 MySQL 事务版本的特性,在 s2 的生命期内,它读到的值不会发生变化。

fn main() {
    let mut s1: String = "neojos".to_string();
    let s2 = &mut s1;
    drop(s2);
    s1 = "change name".to_string();
    print!("{}", s2)
}

这次强制 drop 掉变量 s2,这里其实是盲目地使用 drop 函数。正常来说,当一个变量离开它的作用域时,该类型实现的 drop 函数会自动被调用,属于析构函数的性质。

这是 drop 函数的解释说明:This method is called implilcitly when the value goes out of scope, and cannot be called explicitly (this is compiler error E0040). 奇怪,我这种主动调用的形式,也没有什么编译问题。

rust 引用怎么用,rust 编程开发,rust,开发语言,后端

之前还只是一个报错,在之前的机器上,现在多出来一个报错

  1. error[E0506]: cannot assign to s1 because it is borrowed 不能给一个被借用的值重新赋值
  2. error[E0382]: borrow of moved value: s2,借用了一个所有权发生转移的值。

这么看来,drop 函数并没有起到它的作用,我们还可以添加作用域来触发 drop 函数,但对应的代码就需要做调整。下面的代码可以正常编译运行,但已经不是原来的代码了。之前代码的意图是:

  1. 给 s1 赋值
  2. 将 s2 设置为 s1的引用
  3. 修改 s1 的值
  4. 打印 s2 的值

再看看下面的代码,虽然编译成功了,但已经和我想验证的南辕北辙了。通过上述的过程可以发现,rust 禁止了这样的过程,我们在引用一个变量的时候,完全不需要担心变量的值会发生更改。

fn main() {
    let mut s1: String = "neojos".to_string();
    {
        let s2 = &mut s1;
        print!("{}", s2)
    }
    s1 = "change name".to_string();
}

升级篇

下面的例子可以正常编译运行,输出结果为 false。第一次接触 rust 的话,对 &4、&false 这样的写法会感到奇怪,Go 语言是不可以对常量取地址的。

例子在做的事情很简单,但我们还是详细介绍一下代码细节。在业务代码中,将数组转换为字典算是一个比较常规的操作,数组查询需要遍历整个数组,而字典只需要O(1)的时间复杂度。

  1. 使用 use 将 HashMap 引入到当前作用域,HashMap 默认不在标准库中,这一点还是挺意外的
  2. 使用 vec 宏创建一个数组,调用 new 方法来创建一个空的字典
  3. 遍历数组,通过 insert 来向字典中添加元素
  4. 判断常量 4 是否在字典中,如果在的话,is_ok 返回 true,否则返回 false
  5. 打印 is_ok 的结果
use std::collections::HashMap;

fn main() {
    let exist_types = vec![1, 2, 3];
    let mut dicts = HashMap::new();

    for index in exist_types {
        dicts.insert(index, true);
    }

    let is_ok = match dicts.get(&4) {
        None => &false,
        Some(res) => res,
    };

    print!("is exist:{}", is_ok);
}

在 for 循环之后,我们尝试输出数组 exist_types 数组,很遗憾,程序编译报错,变量 exist_types 在 for 循环遍历中发生了所有权转移。就是说,for 循环执行完成之后,数组就变成初始化状态了。

如下代码,①标注了我们增加打印输出的位置。报错提示: borrow of moved value: exist_types 。格式化宏 print! 中的参数并不会发生变量所有权转移,它只会借用参数的共享引用。

use std::collections::HashMap;

fn main() {
    let exist_types = vec![1, 2, 3];
    let mut dicts = HashMap::new();

    for index in exist_types {
        dicts.insert(index, true);
    }

	// ①
    print!("{:?}\n", exist_types);

    let is_ok = match dicts.get(&4) {
        None => &false,
        Some(res) => res,
    };

    print!("is exist:{}", is_ok);
}

既然这样,下面将 for 遍历的对象修改为数组的引用。for 语句的遍历对象变成了数组的引用,程序可以正常编译执行,数组也得到了正常的打印。

可别小看这一个地方的修改,因为这个地方的修改,字段的类型也会发生变化。之前字段的类型是 HashMap<i32,bool>,现在的类型是 HashMap<&i32,bool>。还需要特别注意另外一点,遍历 exist_types 的元素类型是 i32,遍历 &exist_types 的元素类型是 &i32。

use std::collections::HashMap;

fn main() {
    let exist_types = vec![1, 2, 3];
    let mut dicts = HashMap::new();
	
	// ② 
    for index in &exist_types {
        dicts.insert(index, true);
    }

    print!("{:?}\n", exist_types);

    let is_ok = match dicts.get(&4) {
        None => &false,
        Some(res) => res,
    };

    print!("is exist:{}", is_ok);
}

如果想继续保持 dicts 的类型是 HashMap<i32,bool>,我们可以在 insert 的时候对引用取值,修改图中 ③标注的位置。程序依旧可以正常执行。但其实多了一丝困惑,为什么在 insert 的时候对 *index 取地址,并没有发生所有权的转移。

本来确实应该发生所有权转移的,但因为这个类型是基础的 int32 类型,基础的数字类型在相互赋值的时候不会发生所有权转移,赋值过程会其实是拷贝。

既然这样,有必要将数组的类型调整成字符串类型,再来验证一番。

use std::collections::HashMap;

fn main() {
    let exist_types = vec![1, 2, 3];
    let mut dicts = HashMap::new();

    for index in &exist_types {
    	// ③
        dicts.insert(*index, true);
    }

    print!("{:?}\n", exist_types);

    let is_ok = match dicts.get(&4) {
        None => &false,
        Some(res) => res,
    };

    print!("is exist:{}", is_ok);
}

在上面例子的基础上,我们转换为字符串来验证。为了方便理解,特意将变量的类型做了明确声明,其实吧,编译可以默认帮我们做了这些事情。

程序还是可以正确运行,说明数组中元素的所有权并没有转移到 HashMap 中。问题来了,为什么没有进行所有权的转移呢?问题出在数组的元素类型上,因为数组的元素类型是 &str,本身就是一个引用类型,在整个程序的执行过程中,都不会发生所有权的转移。

既然知道的问题的原因,我们继续通过改造这个例子来验证参测。没有必要继续使用 HashMap 这个结构,我们可以通过更简单的例子来验证数组中变量的所有权转移。

use std::collections::HashMap;

fn main() {
    let exist_types: Vec<&str> = vec!["1", "2", "3"];
    let mut dicts: HashMap<&str, bool> = HashMap::new();

    for index in &exist_types {
        dicts.insert(*index, true);
    }

    print!("{:?}\n", exist_types);

    let is_ok: &bool = match dicts.get("4") {
        None => &false,
        Some(res) => res,
    };

    print!("is exist:{}", is_ok);
}

看下面这个例子,首先,明确声明了变量 first ,类型为 String。继续对数组引用进行遍历,只不过,这次我们强制使用 **index 来获取引用的值。我想:既然 index 类型是 &&str,那直接使用 **index 势必可以获取到 str 类型,对 **index 进行赋值操作,应该可以触发所有权转移了吧。

事与愿违,编译器有报错提示,又触发了另外一个问题,str 变量的尺寸未知。

  1. the size for values of type str cannot be known at compilation time

这么看来,对 str 这个类型还真是没有办法了,直接声明为 str 类型,编译器无法确定它的内存大小,声明为 &str 又是一个引用类型,无法触发所有权转移。既然如此,现在唯一的办法就是使用 String 这个类型

fn main() {
    let first = "1".to_string();
    let exist_types: Vec<&str> = vec![&first, "2", "3"];

    let take_first: str;
    for index in &exist_types {
        take_first = **index;
        break;
    }

    print!("{:?}\n", exist_types);
}

下面的代码和前面的 HashMap 代码唯一的差别是:数组类型变成 String 了,对应的 HashMap 的类型也得跟着调整。迫不及待的想要试验一把了。

🙂!有编译报错提示,index 是一个共享的引用,不能对它做所有权转移。这个和 Go 的设计非常接近,在 for 循环的迭代过程中,变量 index 其实是一个值。文章来源地址https://www.toymoban.com/news/detail-590740.html

  1. cannot move out of *index which is behind a shared reference
use std::collections::HashMap;

fn main() {
    let exist_types: Vec<String> = vec!["1".to_string(), "2".to_string(), "3".to_string()];
    let mut dicts: HashMap<String, bool> = HashMap::new();

    for index in &exist_types {
        dicts.insert(*index, true);
    }

    print!("{:?}\n", exist_types);

    let is_ok: &bool = match dicts.get("4") {
        None => &false,
        Some(res) => res,
    };

    print!("is exist:{}", is_ok);
}

到了这里,关于rust 引用怎么用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Rust编程语言入门之智能指针

    指针:一个变量在内存中包含的是一个地址(指向其它数据) Rust 中最常见的指针就是”引用“ 引用: 使用 借用它指向的值 没有其余开销 最常见的指针类型 智能指针是这样一些数据结构: 行为和指针相似 有额外的元数据和功能 通过记录所有者的数量,使一份数据被多个

    2023年04月16日
    浏览(54)
  • Rust编程语言入门之无畏并发

    Concurrent:程序的不同部分之间独立的执行(并发) Parallel:程序的不同部分同时运行(并行) Rust无畏并发:允许你编写没有细微Bug的代码,并在不引入新Bug的情况下易于重构 注意:本文中的”并发“泛指 concurrent 和 parallel 在大部分OS里,代码运行在进程(process)中,OS同时

    2023年04月19日
    浏览(70)
  • 如何在 macOS 上安装 Rust 编程语言

    安装Rust编程语言在Mac上是一个相对简单的过程,但它可能会涉及多个步骤。在本文中,我将详细说明如何在Mac上安装Rust,并提供一些常见问题的解决方法。请注意,由于软件和工具可能会发生变化,因此建议首先查看Rust官方网站以获取最新的安装说明。 目录 1.打开终端 2

    2024年02月01日
    浏览(58)
  • 【跟小嘉学 Rust 编程】十七、面向对象语言特性

    【跟小嘉学 Rust 编程】一、Rust 编程基础 【跟小嘉学 Rust 编程】二、Rust 包管理工具使用 【跟小嘉学 Rust 编程】三、Rust 的基本程序概念 【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念 【跟小嘉学 Rust 编程】五、使用结构体关联结构化数据 【跟小嘉学 Rust 编程】六、枚举

    2024年02月10日
    浏览(89)
  • 【编程】Rust语言入门第4篇 字符串

    Rust 中的字符是 Unicode 类型,因此每个字符占据 4 个字节内存空间,但字符串不一样,字符串是 UTF-8 编码,也就是字符串中的字符所占的字节数是变化的(1 - 4)。 常见的字符串有两种: str,通常是引用类型, str ,即字符串字面常量,字符串切片。 std::string::String 类型 str 的变

    2024年02月20日
    浏览(56)
  • Rust编程语言入门之函数式语言特性:-迭代器和闭包

    闭包(closures) 迭代器(iterators) 优化改善 12 章的实例项目 讨论闭包和迭代器的运行时性能 闭包:可以捕获其所在环境的匿名函数。 闭包: 是匿名函数 保存为变量、作为参数 可在一个地方创建闭包,然后在另一个上下文中调用闭包来完成运算 可从其定义的作用域捕获值

    2023年04月08日
    浏览(46)
  • Go 与 Rust:现代编程语言的深度对比

    在快速发展的软件开发领域中,选择合适的编程语言对项目的成功至关重要。Go 和 Rust 是两种现代编程语言,它们都各自拥有一系列独特的特性和优势。本文旨在深入比较 Go 和 Rust,从不同的角度分析这两种语言,包括性能、语言特性、生态系统、适用场景以及社区支持。

    2024年04月13日
    浏览(51)
  • Rust编程语言入门之cargo、crates.io

    通过 release profile 来自定义构建 在https://crates.io/上发布库 通过 workspaces 组织大工程 从 https://crates.io/来安装库 使用自定义命令扩展 cargo release profile: 是预定义的 可自定义:可使用不同的配置,对代码编译拥有更多的控制 每个 profile 的配置都独立于其它的 profile cargo 主要的

    2023年04月09日
    浏览(56)
  • 后端开发怎么学?

    后端开发怎么学? 后端开发可以简单地理解为与前端开发相对应的开发方向。前端开发主要负责构建用户界面、维护用户体验等方面的工作,而后端开发则主要负责处理数据、逻辑和算法等方面的工作。后端开发旨在为前端应用程序提供支持,以帮助实现可靠、安全且高效的

    2024年02月20日
    浏览(30)
  • Golang vs Rust ——服务端编程应该选择哪种语言

    为服务端编程选择一种语言应该基于你的长期目标和项目的要求,因此,盲目地问我应该雇用 Go 开发人员还是应该选择 Rust 进行开发并不能帮助你解决问题。 然而,如果你发现自己陷入了困境,那么这篇文章将为你解惑。下面让我们开始吧。 Go 是一种静态类型的、AOT 编译的

    2024年02月02日
    浏览(62)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包