当前位置: 首页 > news >正文

WHAT - 高性能和内存安全的 Rust(二)

目录

  • 1. 所有权(Ownership)
  • 2. 借用(Borrowing)
    • 不可变借用
    • 可变借用
  • 3. 可变性(Mutability)
  • 4. 作用域(Scope)
  • 综合示例

了解 Rust 的所有权(ownership)、借用(borrowing)、可变性(mutability)以及作用域(scope)是掌握 Rust 的关键。

下面通过具体的代码示例来解释这些概念。

1. 所有权(Ownership)

所有权和生命周期是 Rust 最大的特性之一,理解了也就掌握了 80% Rust 的精髓。

  • Rust 中的每一个值都有一个所有者,某个特定的变量
  • Rust 中任何一个值在任何时刻有且只有一个所有者
  • 当所有者离开作用域,值即被丢弃

以下示例展示了基本的所有权规则:

fn main() {let s1 = String::from("hello");let s2 = s1; // s1的所有权被移动到s2// println!("{}", s1); // 这行代码会报错,因为s1不再拥有这个值的所有权println!("{}", s2); // s2现在拥有这个值
}

在这个例子中,s1将其所有权移动到s2,因此s1不再有效。可以尝试在 Rust 在线工具 中运行。

首先要明确值的概念,上述例子中,字符串在堆上被分配的空间以及内容才是真正的值,而 s1s2 是指向这个值的指针,也就是所有者。由于任何一个值在任何时刻有且只有一个所有者,当执行 let s2 = s1; 后,s1 不再拥有值的所有权,因此编译会报错。

如此编译错误也可以避免生产环境的 Use-after-free 漏洞。

再来看另一个例子:

fn main() {let s1 = String::from("hello");let length = calc_length(s1);println!("length of {} is {}.", s1, length);
}fn calc_length(s: String) -> usize {s.len()
}

上述代码会编译错误。可以尝试在 Rust 在线工具 中运行。

问题出来 calc_length,执行该函数时,值的所有权已经有 s1 移交给 s,而因为当所有者离开作用域后对应的值会被丢弃,s 所指向的值就被丢弃了。

想要避免这个编译错误,我们需要把所有权移交出去:

fn main() {let s1 = String::from("hello");let (s2, length) = calc_length(s1);println!("length of {} is {}.", s2, length);
}fn calc_length(s: String) -> (String, usize) {(s, s.len())
}

很不幸,上述代码会有新的编译错误。问题出在 (s, s.len());,因为任何一个值在任何时刻有且只有一个所有者,所有执行完前面一半代码后,(s, s 的所有权已经移交出去了,再执行后一半,s.len()),就会报错,因为 s 在这一刻就不存在了。

继续优化代码:

fn main() {let s1 = String::from("hello");let (s2, length) = calc_length(s1);println!("length of {} is {}.", s2, length);
}fn calc_length(s: String) -> (String, usize) {let length = s.len();(s, length)
}

2. 借用(Borrowing)

上述第二个例子的代码出现一个问题:只想要获取字符串长度,却需要将所有权转移两次。因此,Rust 提供了另外一个重要的概念:借用。

借用是指通过引用传递数据,而不是通过值传递。Rust 区分可变借用和不可变借用。

不可变借用

fn main() {let s1 = String::from("hello");let len = calculate_length(&s1);println!("The length of '{}' is {}.", s1, len);
}fn calculate_length(s: &String) -> usize {s.len() // 借用s,不会改变所有权
}

在这个例子中,calculate_length函数借用了s1的不可变引用,因此calculate_length函数不能修改s1的值。

可变借用

fn main() {let mut s = String::from("hello");change(&mut s);println!("{}", s);
}fn change(s: &mut String) {s.push_str(", world");
}

在这个例子中,change函数借用了s的可变引用,因此可以修改s的值。

由于 Rust 对所有权以及引用有严格的控制,因此 Rust 在任何时间可以准确知晓引用的数量,因此 Rust 可以对引用使用类似读锁(共享锁)以及写锁(排他锁)的控制。换句话说,Rust 中的任何值:

  1. 在任意时间,要么只有一个可变引用(排他锁),要么只能有多个不可变引用(共享锁)
  2. 引用必须总是有效的

上述特性在多线程编程尤为重要,可以很大程度保证代码的线程安全,并且由于这些分析发生在编译时,Rust 可以避免为了保证线程安全带来的额外性能开销。

3. 可变性(Mutability)

Rust 中变量默认是不可变的,需要显式地使用 mut 关键字声明可变变量。

fn main() {let mut x = 5;println!("The value of x is: {}", x);x = 6;println!("The value of x is: {}", x);
}

在这个例子中,x被声明为可变的,因此可以修改其值。

4. 作用域(Scope)

作用域决定了变量在程序中的生命周期。变量一旦离开作用域,其值会被自动销毁。

Rust 和大部分编程语言类似,值的作用域是离它的声明最近的一对花括号中间。

fn main() {{let s = String::from("hello"); // s在这个作用域内有效println!("{}", s);} // 这里s离开作用域并被销毁// println!("{}", s); // 这行代码会报错,因为s已经超出了作用域
}

在这个例子中,s的作用域仅限于花括号内部,一旦超出作用域,s会被销毁,无法再访问。

综合示例

下面的示例综合展示了所有权、借用、可变性和作用域:

fn main() {let mut s = String::from("hello");{let r1 = &s; // 不可变借用let r2 = &s; // 不可变借用println!("r1: {}, r2: {}", r1, r2);} // r1和r2在这里离开作用域{let r3 = &mut s; // 可变借用r3.push_str(", world");println!("r3: {}", r3);} // r3在这里离开作用域println!("s: {}", s);
}

这个例子展示了如何在不同作用域内进行不可变和可变借用,以及如何在作用域结束时自动释放借用。

通过这些示例,可以更好地理解Rust中的所有权、借用、可变性和作用域的基本概念。

http://www.lryc.cn/news/381814.html

相关文章:

  • 办理河南建筑工程乙级设计资质的流程与要点
  • 分类算法和回归算法区别
  • 利用Frp实现内网穿透(docker实现)
  • 怎么用Excel生成标签打印模板,自动生成二维码
  • java基于ssm+jsp 美食推荐管理系统
  • 数据分析:置换检验Permutation Test
  • 【React】使用Token做路由权限控制
  • 机器学习周记(第四十四周:Robformer)2024.6.17~2024.6.23
  • JAVA学习笔记DAY10——SpringBoot基础
  • 如何在Android中实现多线程与线程池?
  • SCI绘图【1】-不同颜色表示密度和差异--密度图
  • C语言 while循环1
  • [C++][数据结构][LRU Cache]详细讲解
  • 怎样激励员工积极应用新版FMEA培训后的知识?
  • WDF驱动开发-WDF总线枚举(一)
  • React useEffect 执行时机
  • centos7 根目录扩容
  • 为什么要做Redis分区和分片
  • 电脑不小心删除的文件怎么恢复?4个必备恢复方法!
  • GPTCache:革新大模型缓存,降低成本,提升效率
  • [Day 15] 區塊鏈與人工智能的聯動應用:理論、技術與實踐
  • 绘唐3下载地址
  • 两个基因相关性细胞系(CCLE)(升级)
  • 2024全国各地高考录取分数线一览表(含一本、二本、专科)
  • 汇编快速入门
  • Apache Tomcat 10.1.25 新版本发布 java 应用服务器
  • 数据类型 运算符
  • WordPress网创自动采集并发布插件
  • (十三)、MQTT3.1.1-MQTT服务端数据结构设计
  • StackOverFlowError常见原因及解决方法总结