在 Rust 中创建零向量:底层魔法

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

Rust 中的零向量:并非看起来那么简单



乍一看,创建一个填充为零的向量能有多难?在 Python 中,一行代码就能搞定:[0] * n。在 JavaScript 中,则是 Array(n).fill(0)。但当你转向 Rust 时,即使是这样一个简单的任务,也会打开一个系统编程、内存管理和零成本抽象的全新世界。



Stack Overflow 上关于 "Creating a vector of zeros for a specific size" 的问题已经积累了数千次浏览。这并非偶然:在这个简单的请求背后,隐藏着对 Rust 如何处理内存、初始化和安全性的深刻理解。今天,我们将深入 Rust 的底层,剖析创建零向量的所有方法,更重要的是,理解内存中实际发生了什么。



为什么这比在 Python 中更复杂?



在 Python 或 JavaScript 中,你只需创建一个数组,解释器就会替你完成所有脏活累活。而 Rust 是一门系统级语言。它不允许内存保持未初始化状态(除非你使用 unsafe)。每个字节要么被显式填充,要么被标记为“未定义行为”。



想象一下:你订购了 100 个空盒子。在 Python 中,他们会直接给你送来 100 个空盒子。而在 Rust 中,快递员会问:“每个盒子里要放什么?”——如果你不说,他根本就不会来。这保证了你永远不会意外地读取到上一个程序留在内存中的“垃圾”。



方法 #1:经典方法 vec![0; size]



最直接且推荐的方法是使用 vec! 宏:



let zeros: Vec<i32> = vec![0; 10];println!("{:?}", zeros); // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]


简洁、优雅、易懂。但底层发生了什么?让我们逐步分析。



vec![0; 10] 的底层原理



当你编写 vec![0; 10] 时,Rust 会执行以下操作:



  1. 在堆上分配内存,用于存放 10 个 i32 类型的元素。每个 i32 占用 4 个字节,总共 40 个字节。
  2. 检查该类型是否实现了 Clone trait。i32 数字实现了它,因此编译器可以为每个元素复制值 0。
  3. 在 alloc::vec::from_elem 内部启动一个复制循环。如果类型是简单的标量类型,编译器会将该循环优化为 memset。
  4. 返回一个 Vec,其长度为 10,容量也为 10。


关键点在于:内存不会保持未初始化状态。Rust 保证在你访问向量之前,每个字节都会被零填充。



方法 #2:vec![Default::default(); size]



如果你的类型不是数字,但实现了 Default trait,你可以使用泛型方法:



#[derive(Default)]struct Point {    x: f64,    y: f64,}

let points: Vec<Point> = vec![Default::default(); 5];println!("{:?}", points[0]); // Point { x: 0.0, y: 0.0 }


对于原始类型,Default::default() 会给出 0、0.0、false 或 '\\0'。对于自定义结构体,则是你在 #[derive(Default)] 中定义的内容。



性能如何?



让我们编写一个简单的基准测试(在实际代码中请使用 criterion):



use std::time::Instant;

fn main() { let n = 1_000_000;

let start = Instant::now(); let v1 = vec![0u8; n]; let dur1 = start.elapsed();

let start = Instant::now(); let v2 = vec![0u64; n]; let dur2 = start.elapsed();

println!("u8: {:?}", dur1); println!("u64: {:?}", dur2);}


在

博客

书籍推荐