前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >66个让你对Rust又爱又恨的场景之一:变量与值

66个让你对Rust又爱又恨的场景之一:变量与值

原创
作者头像
程序员吾真本
发布于 2024-07-18 02:04:44
发布于 2024-07-18 02:04:44
55300
代码可运行
举报
运行总次数:0
代码可运行

讲动人的故事,写懂人的代码

传统编程语言的堆内存管理机制,一般分为手动内存管理和垃圾回收两种流派。

属于手动内存管理流派的C++,虽然提供了手动管理内存的灵活性,但容易因程序员的失误导致内存泄漏、悬垂指针、双重释放和野指针等问题。

属于垃圾回收流派的Java,虽然省去了手动内存管理,但带来了内存释放不可预知且系统开销较大的问题。

另外,在多线程环境中,多个线程同时访问和修改同一块内存时,可能会发生数据竞争,导致未定义行为或数据损坏。

该如何解决这些问题?Rust的解决方案是实现编译器参与检查的“出域即清”内存自动释放机制。这使Rust成为以内存安全著称的编程语言。

Rust编译器参与检查“出域即清”内存自动释放机制,指当堆上值、栈上值和其他系统资源(如文件句柄)的所有者超出作用域时,Rust会自动释放该值所占用的内存资源(对于大多数类型无须显式编写内存释放代码),或关闭相关资源(需要显式编写资源关闭代码,以便Rust调用)。同时,在编译阶段,通过Rust编译器,尤其是其内部的借用检查器(borrow checker),对代码进行全面分析。它不仅能检查“出域即清”机制的正确应用,还能验证更广泛的所有权和借用规则。这包括检测潜在的内存泄漏、悬垂指针、数据竞争等问题。

通过这种机制,Rust能在编译时发现违背所有权机制规则的源代码,并给出明确的错误提示,要求程序员修改。这样做的目的是将大量可能在运行时出现的bug,消灭在编译阶段,极大地节省了返工成本,提高了程序的内存安全性和并发安全性。

除了编译时检查,Rust还保留了一些必要的运行时安全检查,如数组边界检查,以提供额外的安全保障。这种多层次的安全机制使Rust在保证高性能的同时,大幅度降低内存相关错误和并发问题的风险。

Rust编译器参与检查“出域即清”的内存自动释放机制虽然好处多多,但它涉及变量、堆上值、栈上值、不可变借用和可变借用这5个角色,分别在所有权、所有权移动、作用域、生存期、丢弃和复制这6个方面的30种场景(注意,栈上值与堆上值在作用域方面不适用,所以应该是28种场景)。

如果再把1个适合单线程和多线程开发的智能指针Box<T>,4个适合单线程开发的智能指针Rc<T>、RefCell<T>、Ref<T>、RefMut<T>,以及5个适合多线程开发的智能指针Arc<T>、Mutex<T>、MutexGuard<T>、RwLock<T>、RwLockReadGuard<T>和RwLockWriteGuard<T>,一共11个智能指针都补充到之前的5个角色里,与那6个方面组合起来,那么场景会陡增到66个!

怎么会是66个?16乘以6,应该是96个场景呀。为了减少你的焦虑,我把其中8个智能指针中联系紧密的分为一组。这样8个智能指针就分成了3组。让每组充当一个角色,就能减少场景数量。再减去前面说到的2个不适用的场景,实际共计64个场景。但66比较好记,所以标题就写成66个场景,如表1所示。

表1 Rust所有权机制的66个场景

角色/方面

所有权

所有权移动

作用域

生存期

丢弃

复制

变量

1. 变量关于所有权场景规则

2. 变量关于所有权移动场景规则

3. 变量关于作用域场景规则

4. 变量关于生存期场景规则

5. 变量关于丢弃场景规则

6. 变量关于复制场景规则

栈上值

7. 栈上值关于所有权场景规则

8. 栈上值关于所有权移动场景规则

9. 栈上值关于作用域场景规则(不适用,因为栈上值本身没有作用域)

10. 栈上值关于生存期场景规则

11. 栈上值关于丢弃场景规则

12. 栈上值关于复制场景规则

堆上值

13. 堆上值关于所有权场景规则

14. 堆上值关于所有权移动场景规则

15. 堆上值关于作用域场景规则(不适用,因为堆上值本身没有作用域)

16. 堆上值关于生存期场景规则

17. 堆上值关于丢弃场景规则

18. 堆上值关于复制场景规则

不可变引用

19. 不可变引用关于所有权场景规则

20. 不可变引用关于所有权移动场景规则

21. 不可变引用关于作用域场景规则

22. 不可变引用关于生存期场景规则

23. 不可变引用关于丢弃场景规则

24. 不可变引用关于复制场景规则

可变引用

25. 可变引用关于所有权场景规则

26. 可变引用关于所有权移动场景规则

27. 可变引用关于作用域场景规则

28. 可变引用关于生存期场景规则

29. 可变引用关于丢弃场景规则

30. 可变引用关于复制场景规则

Box<T>

31. Box<T>关于所有权场景规则

32. Box<T>关于所有权移动场景规则

33. Box<T>关于作用域场景规则

34. Box<T>关于生存期场景规则

35. Box<T>关于丢弃场景规则

36. Box<T>关于复制场景规则

Rc<T>

37. Rc<T>关于所有权场景规则

38. Rc<T>关于所有权移动场景规则

39. Rc<T>关于作用域场景规则

40. Rc<T>关于生存期场景规则

41. Rc<T>关于丢弃场景规则

42. Rc<T>关于复制场景规则

RefCell<T>

43. RefCell<T>关于所有权场景规则

44. RefCell<T>关于所有权移动场景规则

45. RefCell<T>关于作用域场景规则

46. RefCell<T>关于生存期场景规则

47. RefCell<T>关于丢弃场景规则

48. RefCell<T>关于复制场景规则

Arc<T>

49. Arc<T>关于所有权场景规则

50. Arc<T>关于所有权移动场景规则

51. Arc<T>关于作用域场景规则

52. Arc<T>关于生存期场景规则

53. Arc<T>关于丢弃场景规则

54. Arc<T>关于复制场景规则

Mutex<T>

55. Mutex<T>关于所有权场景规则

56. Mutex<T>关于所有权移动场景规则

57. Mutex<T>关于作用域场景规则

58. Mutex<T>关于生存期场景规则

59. Mutex<T>关于丢弃场景规则

60. Mutex<T>关于复制场景规则

RwLock<T>

61. RwLock<T>关于所有权场景规则

62. RwLock<T>关于所有权移动场景规则

63. RwLock<T>关于作用域场景规则

64. RwLock<T>关于生存期场景规则

65. RwLock<T>关于丢弃场景规则

66. RwLock<T>关于复制场景规则

这66个场景形成了Rust陡峭的学习曲线,让很多初学者望而却步。

只有搞清这66种场景,才能翻越陡峭的Rust学习高峰,赶走Rust入门的拦路虎。

在介绍这66个场景之前,先熟悉一下11个角色和6个方面。

1. 参与所有权机制的角色

Rust的所有权机制涉及多个角色。这些角色可以分为三类,即变量、引用和智能指针。这些角色在不同场景下发挥着各自的作用。

变量是最基本的角色,它拥有栈上值或堆上值。当一个变量离开作用域时,它所拥有的值也随之被释放。

引用则是对变量所拥有的值的借用,分为不可变引用和可变引用。在同一作用域内,要么只能有一个可变引用,要么可以有多个不可变引用。但不能同时存在可变和不可变引用。

智能指针是更高级的抽象,它们在实现上利用了Rust的所有权规则。但提供了如下更灵活方便的使用模式。

Box<T>提供了堆内存分配,常用于表达递归的数据结构。

Rc<T>通过引用计数实现共享不可变所有权,适合单线程内表达数据结构。

RefCell<T>提供了运行时借用检查,可以在运行时动态检查借用规则,在回调函数这样的场景下,比编译时检查更为灵活。

Ref<T>RefMut<T>RefCell<T>的两个关联类型,它们分别代表了RefCell<T>的不可变借用和可变借用。

Arc<T>Rc<T>多线程版本。

Mutex<T>RwLock<T>用于多线程内的共享可变访问,其中Mutex<T>适合写操作较多的场景,而RwLock<T>适合读操作较多的场景。

MutexGuard<T>Mutex<T>的一个关联类型,代表了对Mutex<T>的锁定和访问。RwLockReadGuard<T>RwLockWriteGuard<T>RwLock<T>的两个关联类型,分别代表了对RwLock<T>的读锁定和写锁定。

1.1. 拥有值的变量

为了给所存储的值起名字,我们需要变量。变量(variable)是用于存储数据的命名空间。

与许多其他编程语言不同,Rust默认情况下变量的值是不可变的,这意味着一旦变量被赋值,它的值就不能再被改变。这个特性有助于提高程序的内存安全性和可预测性。

在Rust中,变量作为值的所有者,遵循所有权规则。每个值在任一时刻只能有一个所有者。当变量离开其作用域时,如果它仍然拥有某个值的所有权,该值会被丢弃,相关的内存(无论是在栈上还是堆上)都会被释放,如代码清单1所示。

代码清单1 Rust 变量可变性与所有权示例

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
1  fn main() {
2      let x = 5;
3      // x = 10;
4  
5      let mut y = 10;
6      y = 20;
7  
8      {
9          let z = String::from("hello");
10         println!("{}", z);
11     }
12 
13     // println!("{}", z);
14 }
// 运行结果:
// hello

代码清单1解释如下。

第2行:声明了一个不可变变量x,并将其与值5绑定。这体现了Rust默认情况下变量的值是不可变的特性。

第3行:如果取消注释,会导致编译错误“cannot assign twice to immutable variable x”,因为x是不可变的,不能被重新赋值。

第5行:使用mut关键字声明了一个可变变量y

第6行:对可变变量y进行重新赋值,这是允许的。

第8-11行:创建了一个新的作用域,并在其中声明并绑定了变量z

第9行:z被与一个String类型的值绑定,z成为这个值的所有者。

第11行:作用域结束,z离开作用域,它拥有的String值被丢弃,相关内存被释放。这体现了所有权规则和作用域结束时的自动清理。

第13行:如果取消注释,会导致编译错误“cannot find value z in this scope”,因为z已经离开作用域,不能再被使用。这再次体现了所有权规则。

Rust的变量拥有值的过程,可以通过所有权转移(如变量赋值、函数调用或函数返回值等)来改变。对于实现了 Copy trait 的类型,则会进行值的复制而非所有权转移。Rust 还提供了借用机制,允许在不转移所有权的情况下临时使用值。

在C++中,与Rust不同,C++默认情况下变量是可变的。如果想让变量不可变,需要使用const关键字。

C++没有像Rust那样的所有权系统,但它提供了手动内存管理机制。

C++的变量的生命周期由其作用域决定,当离开作用域时,栈上的变量会自动销毁。

与C++类似,Java变量默认也是可变的。要创建不可变变量,需要使用final关键字。

对于引用类型,Java变量存储的是对象的引用,而非对象本身。

Java使用自动垃圾回收机制管理内存,无需手动释放。

1.2. 访问快捷的栈上值

为了存储局部变量、函数调用信息和在编译时大小已知且固定的值,我们需要栈上值。

栈(stack)是一种快速的内存分配区域,用于存储在编译时大小已知且固定的值。

在Rust中,典型的栈上值包括基本类型(如整型、浮点型、布尔型和字符型)以及包含这些类型的数组和元组。

Rust的栈上值具有以下优势。首先是访问速度快,因为遵循后进先出(LIFO)的原则,栈上值的分配和释放非常快速。其次是自动管理内存,不需要手动分配和释放内存。第三是确定性,栈上值的生命周期在编译时就能确定。

Rust的栈上值有以下劣势。首先是大小限制,栈空间通常较小,不适合存储大量数据。其次是固定大小,栈上值的大小必须在编译时确定,不能动态改变。

Rust的栈上值适用于以下场景。首先是存储小型、固定大小的数据。其次是存储需要快速访问的临时变量。最后是存储函数参数和返回值(当它们是固定大小时),如代码清单2所示。

代码清单2 Rust栈上值示例:基本类型、数组、元组和函数调用

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 1  fn main() {
 2      let integer: i32 = 42;
 3      let float: f64 = 3.14;
 4      let boolean: bool = true;
 5      let character: char = 'A';
 6
 7      let array: [i32; 5] = [1, 2, 3, 4, 5];
 8      let tuple: (i32, f64, bool) = (10, 2.5, false);
 9
10      let result = calculate_sum(integer, array[0]);
11      println!("Result: {}", result);
12  }
13
14  fn calculate_sum(a: i32, b: i32) -> i32 {
15      let sum = a + b;
16      sum
17  }
// 运行结果:
// Result: 43

代码清单2解释如下。

第2-5行:展示了Rust中典型的栈上值,包括基本类型(整型、浮点型、布尔型和字符型)。这些都是在编译时大小已知且固定的值。

第7-8行:演示了包含基本类型的数组和元组,它们也是栈上值。

第10行:调用函数calculate_sum,展示了函数调用信息存储在栈上。参数integerarray[0]都是栈上值。

第14行:定义了一个名为 calculate_sum 的函数,接受两个 i32 类型的参数 ab,并返回一个 i32 类型的值。

第14-17行:calculate_sum函数定义,展示了函数参数和返回值(固定大小)存储在栈上。sum是一个局部变量,也存储在栈上。

第16行:函数最后一个不带分号的表达式sum,就是这个函数的返回值。

与Rust的栈上值相似,C++的栈上值同样包括基本类型、固定大小的数组、结构体和非动态分配的类对象。C++的栈上值也具有快速访问和自动内存管理的优势。通常,C++栈上值的生命周期也是可预测的,基于其所对应的变量的作用域。

C++的栈上值与Rust的栈上值相比存在以下区别。首先是安全性,C++缺乏Rust的所有权系统和借用检查器,可能导致一些内存安全问题。其次是未定义行为,C++允许一些可能导致未定义行为的操作,如返回局部变量的引用,这在Rust中是被禁止的。最后是编译时检查,虽然C++栈上值的生命周期通常可预测,但缺乏Rust那样严格的编译时生命周期检查。

Java的栈上值处理与Rust有显著差异,主要体现在以下方面。

  • 类型限制:Java的栈仅用于存储基本类型值和对象引用,而不存储完整的对象。这与Rust可以在栈上存储完整结构体的做法不同。
  • 对象存储位置:Java中所有对象实例(包括数组)都存储在堆上,栈只存储对这些对象的引用。这与Rust可以在栈上存储完整对象的能力形成对比。
  • 生命周期管理:对于基本类型,行为类似Rust和C++中的栈上值。对于对象类型,生命周期由垃圾回收器管理,不完全由编译时的作用域决定。这与Rust的确定性生命周期管理形成鲜明对比。
  • 内存管理:Java依赖垃圾回收器自动管理内存,而Rust通过所有权系统在编译时管理内存。这导致Java在运行时的内存管理开销较大,但编程较为简单。
  • 性能考虑:由于Java的对象分配在堆上,并依赖垃圾回收,在处理大量小对象时可能比Rust的栈分配方式效率更低。
  • 编译时保证:Java缺乏Rust那样严格的编译时内存安全检查,更多依赖于运行时检查和垃圾回收。

1.3. 可动态分配的堆上值

为了存储在编译时大小未知,或在运行时大小可能会改变的值,我们需要堆上值。

堆(heap)是一种动态内存分配区域。堆上值是那些因为在编译时大小未知,或者在运行时大小可能会改变,而需要存储在堆内存上的数据。

在Rust中,通常使用Box<T>Vec<T>String等智能指针类型来在堆上分配内存。在Rust中,堆内存的管理方式与C++有很大不同。Rust采用了一种独特的内存管理模型,它既不需要程序员手动管理内存,也不依赖垃圾回收器,而是凭借所有权机制、借用机制、生存期、智能指针、Drop trait和编译时检查,保证内存安全,同时也实现了高性能。

Rust的堆上值具有以下优势。首先是动态大小,堆允许在运行时动态分配之前未知大小的数据。其次是长生命周期,堆上的数据可以存活超过创建它的作用域。最后是大量数据,适合存储大量数据,而不受栈大小限制。

Rust的堆上值有以下劣势。首先是性能开销,堆分配比栈分配慢,且需要手动或自动的内存管理。其次是缓存效率,堆上的数据可能分散在内存中,影响缓存效率。

Rust的堆上值适用于以下场景。首先是当数据大小在编译时未知时。其次是当需要数据在多个作用域间共享时。最后是实现递归数据结构如链表或树时。如代码清单3所示。

代码清单3 Rust堆上值示例:智能指针与动态数据结构

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
1  use std::rc::Rc;
2
3  struct Node {
4      value: i32,
5      next: Option<Rc<Node>>,
6  }
7
8  fn main() {
9      let mut vec = Vec::new();
10     vec.push(42);
11
12     let boxed_value = Box::new(100);
13
14     let mut string = String::from("Hello");
15     string.push_str(", world!");
16
17     let node1 = Rc::new(Node {
18         value: 1,
19         next: None,
20     });
21
22     let node2 = Rc::new(Node {
23         value: 2,
24         next: Some(Rc::clone(&node1)),
25     });
26
27     println!("Vec size: {}", vec.len());
28     println!("String content: {}", string);
29     println!("Boxed value: {}", boxed_value);
30     println!("Node2 value: {}", node2.value);
31 }
// 运行结果:
// Vec size: 1
// String content: Hello, world!
// Boxed value: 100
// Node2 value: 2

代码清单3解释如下。

第1行:引入标准库中的Rc(引用计数智能指针),允许多所有者。

第3行:定义一个结构体Node,用来表示链表节点。

第4行:结构体中的一个字段value,类型为i32,表示节点的值。

第5行:结构体中的另一个字段next,类型为Option<Rc<Node>>,表示下一个节点的引用,使用Rc允许多个节点共享同一个下一个节点。

第5行中的Option是Rust标准库中的一个枚举,用来表示一个值可能存在也可能不存在的情况。具体来说,Option<T>定义如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
enum Option<T> {
    Some(T),
    None,
}

这个枚举有两个变体:

  • Some(T):表示存在一个值,值的类型为T
  • None:表示没有值。

在第5行中,next字段的类型为Option<Rc<Node>>,其含义是这个字段可以有两种状态:

  • Some(Rc<Node>):表示存在下一个节点,并且这个节点是通过引用计数智能指针Rc进行引用的。
  • None:表示不存在下一个节点,即这是链表的终止节点。

使用Option枚举的好处是,它强制程序员显式地处理可能不存在的值,从而提高代码的安全性和健壮性。例如,在访问next字段时,必须先检查它是否为Some,否则会遇到编译错误,这避免了很多空指针异常的潜在问题。

在实际代码中,我们看到第19行node1next字段被设置为None,表示node1是链表的终止节点。

node2next字段被设置为Some(Rc::clone(&node1)),表示node2的下一个节点是node1

这种设计使得链表节点可以灵活地表示是否有下一个节点,从而实现了更安全和健壮的链表结构。

第9行:声明一个可变的空向量veclet关键字用来声明变量。mut关键字表示这个变量是可变的,意味着可以对它进行修改操作(例如添加或删除元素)。vec是变量名,用来引用这个动态数组。

第9行中的Vec是Rust标准库中的动态数组类型,提供了一个可变长度的序列。Vec类型的全称是Vec<T>,其中T表示向量中元素的类型。在这一行中,Vec用于创建一个动态数组,可以根据需要添加、删除或访问元素。

Vec::new()是一个关联函数(即静态方法),用于创建一个新的、空的Vec。这个函数返回一个空的动态数组,其初始容量为零,但会根据需要自动调整大小。

Vec类型具有以下特点。

  • 动态数组:Vec的长度是可变的,可以根据需要动态增加或减少元素。
  • 高效:Vec在堆上分配内存,并且通常会预留比当前需要更多的空间,以减少频繁的内存分配和复制操作。
  • 灵活:可以存储任何类型的元素,只需在声明时指定类型参数。例如,Vec<i32>表示存储i32类型的整数。
  • 方便:提供了丰富的方法,例如push(添加元素)、pop(移除元素)、len(获取长度)等。

第10行:向向量vec中添加一个值42。演示了堆上值的动态大小特性。

第12行:使用Box<T>在堆上分配一个整数,展示了智能指针的使用。

第14行:将初始值为"Hello"绑定到一个可变字符串变量string上。

第15行:向字符串string中追加", world!"。说明了堆上值在运行时可以改变大小。

第17行:创建第一个节点node1,使用Rc包装以便在第24行共享所有权,即node2在第24行和node1共享这一行所创建的node1的不可变所有权。

第18行:node1value字段赋值为1

第19行:node1next字段赋值为None,表示这是链表的终止节点。

第22行:创建第二个节点node2,同样使用Rc包装。

第23行:node2value字段赋值为2

第24行:node2next字段指向node1,使用Rc::clone增加引用计数。这展示了如何在多个作用域间共享数据。

第24行中的Rc代表引用计数(Reference Counting),是一种智能指针,允许多所有者共享同一个数据。当你调用Rc::clone时,并不会创建数据的副本,而是增加引用计数。这使得多个变量可以安全地共享同一个数据。在这里:Rc::clone(&node1) 会增加node1的引用计数,而不会复制node1所指向的Node实例。这样做的好处是,当你需要多个变量引用同一个数据时,不必担心内存管理问题,Rc会自动处理这些引用的计数和释放。

第24行中的&node1 是一个引用,表示对node1的借用。借用的目的是为了只读访问node1,而不是获取其所有权。具体来说,Rc::clone需要一个对Rc<T>的引用作为参数,因此你需要传递&node1而不是node1本身。

第17-25行:使用Rc<T>(引用计数智能指针)创建一个递归数据结构(链表),展示了堆上值适用于实现递归数据结构的场景。

在C++中,堆上值包括使用new运算符动态分配的对象或数组、标准库容器(如std::vectorstd::stringstd::map等)以及任何在运行时需要动态分配内存的数据结构。与Rust不同,C++中程序员需要手动管理堆内存(使用delete释放new所分配的内存),或使用智能指针如std::unique_ptrstd::shared_ptr进行半自动管理。这种方法给予程序员更多控制权,但也增加了内存泄漏和悬垂指针的风险。C++的智能指针提供了类似Rust的所有权语义,但不像Rust那样在编译时强制执行。

C++与Rust关于堆上值有以下区别。首先是内存管理,C++需要手动管理或使用智能指针,Rust使用所有权系统。其次是安全性,C++依赖程序员谨慎性,Rust在编译时强制执行内存安全。最后是性能开销,C++可能因手动管理而略微提高性能,但也增加了出错风险。

在Java中,几乎所有对象都存储在堆上。Java的堆上值包括所有使用new关键字创建的对象、所有数组(无论是对象数组还是基本类型数组)、所有类的实例,包括String、集合类(如ArrayListHashMap)等。与Rust和C++不同,Java中堆内存由垃圾回收器自动管理,程序员不需要手动释放内存。这种方法虽然简化了开发,但也带来了垃圾回收不可预知和较大的系统开销,这是Rust刻意避免的。

Java与Rust关于堆上值有以下区别。首先是内存管理,Java使用垃圾回收,Rust使用所有权系统。其次是性能,Java可能因垃圾回收而有性能波动,Rust提供更可预测的性能。第三是资源管理,Java主要关注内存,Rust的所有权系统不仅适用于内存管理,也适用于其他资源(如文件句柄、网络套接字、数据库连接、线程句柄、锁和互斥量等)。最后是并发安全,Java依赖同步机制,Rust的所有权系统在编译时就能防止数据竞争。

(未完待续。划到文章下方能看目录和上下篇哦~😃)

如果喜欢这篇文章,别忘了给文章点个“赞”,好鼓励我继续写哦~😃

如果哪里没讲明白,就在评论区给我留言哦~😃

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【Rust每周一知】理解智能指针Box<T>
指针是个通用概念,它表示内存地址这种类型,其引用或“指向”其他数据。Rust中的指针是“第一类公民”(first-class values),可以将它们移动或复制,存储到数据结构中并从函数中返回。Rust提供了多种类型的指针:
MikeLoveRust
2020/02/20
2.2K0
66个让你对Rust又爱又恨的场景之二:不可变引用
在Rust中,相比多方为了读取一份数据,而费尽周章地复制整个数据或转移所有权,有时运用不可变借用会更高效,所以我们需要不可变引用。
程序员吾真本
2024/07/19
2860
66个让你对Rust又爱又恨的场景之二:不可变引用
Rust避坑现代C++悬垂指针
C++是一门应用广泛的编程语言。在2023年JetBrains全球开发者生态问卷调查中,C++在受访程序员过去一年中的使用率,占25%,紧跟JavaScript、Python和Java之后。在本书撰写时,根据JetBrains的统计,程序员使用最多的是C++17。
程序员吾真本
2024/09/18
6372
Rust避坑现代C++悬垂指针
【Rust精彩blog】Rust 中几个智能指针的异同与使用场景
想必写过 C 的程序员对指针都会有一种复杂的情感,与内存相处的过程中可以说是成也指针,败也指针。一不小心又越界访问了,一不小心又读到了内存里的脏数据,一不小心多线程读写数据又不一致了……我知道讲到这肯定会有人觉得“出这种问题还不是因为你菜”云云,但是有一句话说得好:“自由的代价就是需要时刻保持警惕”。
MikeLoveRust
2020/02/20
1.9K0
使用默认不可变的Rust变量会踩什么坑
Rust的变量真的是名不副实。名字中明明有个“变”字,却默认不可变。还美其名曰“不可变变量”。要想让变量名副其实,还必须费心额外加个mut关键字,并必须称其为“可变变量”,才能与前者区分开。这两个名字越琢磨越有趣。
程序员吾真本
2024/10/02
3420
使用默认不可变的Rust变量会踩什么坑
《Rust避坑式入门》第1章:挖数据竞争大坑的滥用可变性
赵可菲是一名Java程序员,一直在维护一个有十多年历史的老旧系统。这个系统即将被淘汰,代码质量也很差,每次上线都会出现很多bug,她不得不加班修复。公司给了她3个月的内部转岗期,如果转不出去就会被裁员。她得知公司可能会用Rust重写很多系统,于是就报名参加了公司的Rust培训,希望能够转型。
程序员吾真本
2024/08/29
5800
《Rust避坑式入门》第1章:挖数据竞争大坑的滥用可变性
Rust入坑指南:智能指针
在了解了Rust中的所有权、所有权借用、生命周期这些概念后,相信各位坑友对Rust已经有了比较深刻的认识了,今天又是一个连环坑,我们一起来把智能指针刨出来,一探究竟。
Jackeyzhe
2020/03/12
9030
《Rust for Rustaceans》 样章试译 | 第二章 Rust 基础
本文是对 Jon Gjengset 写的新书 《Rust for Rustaceans》样章第二章的中文试译初稿。出于对 Jon 的尊敬,以及想了解 Jon 眼中的 Rust ,我打算翻译一下这本书。发出来让大家看看翻译效果,欢迎指正。
张汉东
2021/07/14
6K1
【译】Rust与智能指针
如果你一直在订阅这个系列,关于所有权的那篇文章[1]可能给你带来了这种印象——Rust 确实是个好东西,C++不应该在生产环境中使用。智能指针可能会改变你的想法。用现代的话来说,Smart pointers 是指那些有点(嗯......)额外(东西)的指针。他们本质上还是管理其所指向的对象的内存地址,并且当对象不再被使用的时候会将其释放。这消除了很多因不恰当的内存管理而引起的 bug,并使得编程不再那么枯燥乏味。C++智能指针为原始指针提供了一个安全的替代方案,而 Rust 智能指针则在保证安全的前提下扩展了语言功能。
MikeLoveRust
2020/10/26
1.1K0
【译】Rust与智能指针
rust智能指针
智能指针虽然也号称指针,但是它是一个复杂的家伙:通过比引用更复杂的数据结构,包含比引用更多的信息,例如元数据,当前长度,最大可用长度等。引用和智能指针的另一个不同在于前者仅仅是借用了数据,而后者往往可以拥有它们指向的数据,然后再为其它人提供服务。智能指针往往是基于结构体实现,它与我们自定义的结构体最大的区别在于它实现了 Deref 和 Drop 特征:
zy010101
2023/05/09
1.1K0
揭开智能指针 Box 的神秘面纱
熟悉 c++ 的肯定知道 shared_ptr, unique_ptr, 而 Rust 也有智能指针 Box, Rc, Arc, RefCell 等等,本文分享 Box 底层实现
MikeLoveRust
2021/08/13
6140
rust的内存管理
内存管理是rust最有意思的事情了。rust的内存管理有三条准则。 let分配资源 分配会转移所有权,比如赋值直接move了 值和变量在作用域末尾会被清理,释放 drop方法会在释放前调用 rust支持移动语义和复制语义,为此抽象出了两个trait,clone和copy 非堆内存可以使用copy,隐式转化,clone需要显示调用 关于借用的规则,使用& 一个引用的生命周期不能超过其被引用的时间 如果存在一个可变借用,不允许存在其他值 如果不存在可变借用,允许存在多个不可变借用 借用规则方法类型 &self
李子健
2022/05/08
7610
go 开发者的 rust 入门
即:在任意给定时间,要么 只能有一个可变引用,要么 只能有多个不可变引用。引用必须总是有效的。
王磊-字节跳动
2021/11/27
1.9K0
从零开始学C++之对象语义与值语义、资源管理(RAII、资源所有权)、模拟实现auto_ptr<class>、实现Ptr_vector
一、对象语义与值语义 1、值语义是指对象的拷贝与原对象无关。拷贝之后就与原对象脱离关系,彼此独立互不影响(深拷贝)。比如说int,C++中的内置类型都是值语义,前面学过的三个标准库类型string,v
s1mba
2017/12/28
1.8K0
 从零开始学C++之对象语义与值语义、资源管理(RAII、资源所有权)、模拟实现auto_ptr<class>、实现Ptr_vector
一名Java开发的Rust学习笔记
笔者的主力语言是Java,近三年Kotlin、Groovy、Go、TypeScript写得比较多。早年间还写过一些Python和JavaScript。总得来说落地在生产中的语言都是应用级语言,对于系统编程级语言接触不多。但这不妨碍我写下这么一篇笔记,说不定也有一些常年在应用层的同学想领略一下Rust的风采呢。
泊浮目
2024/03/19
2600
一名Java开发的Rust学习笔记
【Rust 基础篇】Rust Box 智能指针
在 Rust 中,Box 是一种智能指针类型,用于在堆上分配内存并管理其生命周期。Box 提供了堆分配的功能,并在所有权转移时负责释放内存。本篇博客将详细介绍 Rust 中 Box 智能指针的使用方法和相关概念。
繁依Fanyi
2023/10/12
5050
Rust 总结
所有权是用来管理堆上内存的一种方式,在编译阶段就可以追踪堆内存的分配和释放,不会对程序的运行期造成任何性能上的损失。
谛听
2022/06/04
1.8K0
Rust中的一些标准库
Box 允许将一个值放在堆上而不是栈上,留在栈上的则是指向堆数据的指针。Box 是一个指向堆的智能指针,当一个 Box 超出作用域时,它的析构函数被调用,内部对象被销毁,堆上的内存被释放。
端碗吹水
2022/06/05
9550
Rust中的一些标准库
【Rust 基础篇】Rust 智能指针
在 Rust 中,智能指针是一种提供了额外功能的指针类型。智能指针可以在编译时和运行时检查内存安全,并提供了更灵活的所有权和借用模型。本篇博客将详细介绍 Rust 中的智能指针,包括常用的智能指针类型、创建和使用智能指针、内存安全和性能考虑等。
繁依Fanyi
2023/10/12
2710
最强肉坦:RUST多线程
这是几乎每种编程语言都会遇到的实现场景,通过对比Java和Rust的实现与运行表现,我们可以清晰地看出Rust的不同或者说Rust的良苦用心,以及为了实现这一切所带来的语言特性。我们首先来看Java的实现方法。
文彬
2022/06/02
1.8K0
相关推荐
【Rust每周一知】理解智能指针Box<T>
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档