Rust教程:内存安全和并发性

最后更新: 04/12/2025
作者: 艾萨克
  • Rust 通过所有权、借用和生命周期在编译时确保内存安全,而无需使用垃圾回收。
  • 类型系统和别名规则允许使用互斥锁、通道和智能指针实现并发而不会出现数据竞争。
  • Cargo、crates.io 和活跃的生态系统简化了依赖项管理、编译、测试和部署。
  • 理解结构体、枚举、Option 和 Result 是处理并发应用程序中的错误和建模安全数据的关键。

C语言与Rust语言:优缺点分析

Rust 已经成为少数几种……的语言之一 每个系统开发人员最终都会一遍又一遍地听到这句话。它的速度堪比 C 和 C++,但却近乎执着地注重内存安全和高效的并发性。这并非空洞的营销噱头:它的设计核心在于编译器能够在编译时检测错误——而这些错误在其他语言中只有在系统投入生产环境运行或崩溃时才会显现。

如果您有兴趣了解 Rust 如何实现无需垃圾回收的内存安全和无需担心数据丢失的并发本教程专为您准备。我们将涵盖从语言及其生态系统的基础知识到所有权、借用、复合类型等关键概念,以及 Cargo 等工具,甚至还会从更易于理解的角度介绍原子类型和锁,以便并发编程新手也能轻松上手,所有内容都将重点放在安全性和性能上。

Rust教程:性能、内存安全和并发性

Rust 是一种编程语言 编程 通用型和多范式,专为……而设计 底层系统编程以及高层项目编程来自 操作系统从游戏引擎和浏览器到高性能网络服务,它起源于 Mozilla,目标是提高软件安全性,尤其是在浏览器引擎等敏感组件方面。

它的主要特征是: 保证编译时的内存安全 Rust 不使用垃圾回收器。它采用所有权系统和借用检查器来跟踪每个值及其引用的生命周期。这避免了悬空指针、缓冲区溢出或内存泄漏等经典问题,而无需自动引用计数或垃圾回收。

此外,Rust 的设计初衷就是为了简化开发过程。 安全出勤它的类型和所有权模型可以防止线程间的数据竞争,至少在安全的 Rust 代码中是如此。这意味着许多危险情况会在编译时被检测到,甚至在执行任何一行代码之前就能检测到。

出于所有这些原因,像大型公司这样的企业 Dropbox、微软、亚马逊或 Google 他们已在其基础设施的关键部分采用了 Rust。Rust 多年来一直位居 Stack Overflow 开发者“最喜爱”语言榜首并非偶然:它结合了 C++ 式的性能、现代化的工具集(Cargo、crates.io)以及非常活跃的社区,即所谓的 Rustaceans。

基本概念:编程语言、类型和内存

在深入探讨内存安全性和并发性的具体细节之前,有必要先澄清一些贯穿全文的通用概念。 El Temppo 使用 Rust 时, 尤其是如果你来自其他语言背景或者刚刚开始学习编程的话。.

编程语言归根结底是…… 一套规则和结构,用于描述算法 并将它们转换为可执行程序。Rust 使用其编译器编译成本地机器代码。 rustc因此,其性能通常与 C 和 C++ 相当。

内存管理是程序对内存进行管理的过程。 运行时预留和释放内存块这方面的错误往往是致命的:内存泄漏(未能释放未使用的内存)、越界写入导致的数据损坏,或者在内存释放后继续使用。Rust 通过非常强大的类型系统以及所有权、借用和生命周期的正式规则来解决这个问题。

Rust 还包含诸如此类的术语 智能类型和指针类型描述了变量存储的数据类型(整数、浮点数、字符串、结构体等)以及如何操作这些数据。智能指针(例如, Box, Rc y Arc) 是封装内存地址并添加额外逻辑以安全地管理资源的结构,例如计算共享引用或将值移动到堆中。

在竞争领域,诸如以下概念 竞态条件、互斥锁和通道 它们变得不可或缺:当多个线程同时访问和修改共享资源而没有适当协调时,就会发生竞争条件;互斥锁(互斥)确保一次只有一个线程进入临界区;通道允许在线程之间发送消息,而无需直接共享内存。

为什么要学习 Rust:内存安全和无畏的并发性

Rust之所以声名鹊起,是因为它提供了 现代编程的三大支柱性能、安全性和现有工具。让我们来看看为什么这些点如此重要。

就性能而言,Rust 直接编译成本地二进制文件 无需虚拟机或解释器。零成本抽象模型旨在确保高级抽象不会在运行时增加额外开销。因此,它非常适合系统开发。 游戏浏览器组件或低延迟微服务。

内存安全基于其 所有权和贷款制度虽然没有垃圾回收器,但编译器能够准确地知道每个资源的归属、何时不再需要以及何时可以释放。这可以防止内存泄漏、悬空指针以及许多其他传统上使 C 和 C++ 编程如此危险的错误。

在竞争领域,Rust 追求的通常是所谓的 “无所畏惧的并发”类型系统本身就阻止了数据根存在于安全代码中。如果要在线程之间共享可变数据,则需要使用适当的原语,例如: Mutex, RwLock o Arc编译器将确保别名和可变性规则得到遵守。

  如何使用 Malwarebytes 逐步清除 Windows 病毒

借助现代工具,开发体验得到提升,例如 货运包机它拥有集成的包管理器和构建基础架构,以及涵盖从异步网络(Tokyo)到 Web 框架(Actix、Rocket、Axum)等各个领域的庞大库(crate)生态系统。所有这一切都由一个开放、活跃且极具耐心的社区提供支持,尤其对初学者而言更是如此。

安装和必备工具:rustup、rustc 和 Cargo

要用 Rust 编写和运行你的第一个程序,通常的入门方法是使用以下命令安装官方工具链: 生锈 (参见) Rust 完全入门),一个简单的安装程序和版本管理器,可在所有主流操作系统上运行。

连接器 生锈 您可以安装、更新和切换不同版本的 Rust(稳定版、测试版、每日构建版),而不会破坏任何现有功能。只需访问 Rust 官方工具页面,并按照适用于您系统的步骤操作即可。安装完成后,编译器即可使用。 rustc项目经理 cargo 和他自己的 rustup 在你的 终端.

编译器 rustc 它将你的源代码转换成可执行的二进制文件或库。虽然你也可以直接调用它。 comandosrustc main.rs实际上,你几乎总是会通过 Cargo 来完成工作,它负责处理对 Cargo 的调用。 rustc 选择正确的选项。

工作流程的核心工具是 货运包机只需几个命令,您就可以在 crates.io 上创建新项目、管理依赖项、编译、运行、测试和发布软件包。一些常用的基本命令包括: cargo new, cargo build, cargo run, cargo test y cargo check它可以检查代码而不生成最终可执行文件,非常适合快速检测错误。

如果你想在不安装任何东西的情况下进行一些尝试, 铁锈游乐场 (官方在线执行器)和 Replit 等平台允许您从浏览器编写和运行小段代码,非常适合试验内存和并发示例,而无需设置整个环境。

你的第一个程序:Hello、Rust 和基本流程

在任何语言中,开启对话的经典方式都是使用著名的“Hello, world”。在 Rust 中,一个文件 main.rs 至少可以包含像函数这样简单的东西。 main 它会在屏幕上打印一个字符串。.

关键词 fn 这表明我们正在定义一个函数,并且 main 这是程序的入口点。函数的代码块放在花括号内。要向控制台写入内容,请使用 println!它接受一个字符串字面量(或带有书签的模板),并将其发送到标准输出,以换行符结尾。

如果你直接编译 rustc main.rs您将获得一个可执行二进制文件(例如, main o main.exe (取决于系统)。运行后,您会在终端看到相关信息。但使用 Rust 的惯用方法是让 Cargo 来主导项目。

连接器 cargo new nombre_proyecto 系统会自动创建文件夹结构。 src/main.rs 已经准备好了“Hello, world”和一个文件 Cargo.toml 其中包含元数据和未来依赖项。从那里开始, cargo run 编译并运行二进制文件只有当检测到更改时才会重新编译。

这种工作方式不仅方便,而且让你从一开始就习惯使用标准的 Rust 生态系统,这在你开始添加用于并发、网络、测试或任何你需要的 crate 时非常有用。

// 我们声明主函数:程序入口点 fn main() { // 我们使用 println! 宏将文本打印到控制台 println!("Hello, world!"); }

变量、可变性和基本数据类型

在 Rust 中,变量使用关键字声明。 let默认情况下 它们是不可变的换句话说,一旦你给它们赋值,除非你显式地将它们声明为可变的,否则你不能修改它们。 mut.

默认情况下,不可变性有助于避免一些细微的逻辑错误,尤其是在并发程序中,多个线程可能想要修改同一个值。如果需要修改,你可以这样写: let mut contador = 0;从那里你可以重新分配新值 contador.

Rust 也允许所谓的 阴影你可以在同一作用域内声明一个同名的新变量,从而隐藏之前的变量。这与修改变量不同,因为你是在创建一个新值(甚至可以是不同的类型)。例如,你可以使用相同的名称将字符串转换为整数,只要它是新的声明即可。 let.

Rust 的类型系统是静态的,这意味着 每个变量的类型在编译时就已经确定了。然而,类型推断非常强大:如果你写 let x = 5;编译器假定它是一个 i32 除非您另行说明。您可以添加备注,例如: let x: i64 = 5; 当你想明确表达的时候。

可用的标量类型包括有符号整数和无符号整数(i8, u8, i32等等),漂浮的那些(f32, f64),布尔(bool)和 Unicode 字符(char). 这些简单类型通常复制成本很低,而且很多类型都实现了该特性。 Copy这意味着,当你将它们赋值或传递给函数时,它们会被复制而不是移动。

Rust 中的字符串:&str 和 String

Rust 中的文本处理一开始可能会让人有点困惑,因为它清楚地区分了…… 连锁“切片”和专有连锁这两个关键部分是 &str y String.

Un &str不可变链的片段存储在某处的 UTF-8 字节序列的视图。典型示例包括字面量,例如: "Hola"它们是以下类型的 &'static str (它们在程序的整个生命周期内都存在,并嵌入在二进制文件中。)切片并不拥有数据;它们只是指向数据。

  如何在 Windows 11 中彻底自定义任务栏:完整指南和高级技巧

String另一方面,是 自定义字符串,可变且托管在堆中。它可以调整大小、连接、通过移动其属性在函数之间传递等等。它常用于构建动态文本或将其长期存储在结构中。

在许多情况下,你会在两者之间进行转换:例如,你会创建一个 String::from("hola") 切片或者你会借用一个 &strString 通过传递对只需要读取数据的函数的引用。

拥有数据和借用数据之间的这种分离是内存管理的关键,并且扩展到了语言的其他部分:集合、结构和枚举都遵循相同的理念,即谁拥有谁只是查看。

函数、控制流和注释

Rust 中的函数是用以下方式定义的: fn 并允许将程序组织成可重用的逻辑单元。每个函数都指定 它的参数类型和返回类型 沿着箭头 ->如果未返回任何有意义的结果,则假定为单一类型。 ().

一个重要的细节是,函数(或任何代码块)中最后一个没有分号的表达式将被视为隐式返回值。你可以使用 return 尽早获得回报但在惯用的代码中,通常只需省略最后一个表达式即可。 ;.

控制流采用经典方式处理。 if/else循环 loop, while y for在 Rust 中, if 这是一个返回值的表达式所以你可以直接在……中使用它 let前提是分支返回的类型相同。循环 for 它们通常遍历范围或集合迭代器,是比手动索引更推荐的选择。

为了记录代码并方便后来者(包括一个月后的你自己)使用,你可以使用 带有行注释的 // 或阻止 /* ... */此外,Rust 还提供了文档注释功能。 /// 这些文档会生成,但这更适合大型项目。

所有权、借贷和生命周期:记忆安全的基础

至此,我们触及了Rust内存模型的核心:系统 所有权、借贷和寿命这些规则确保引用始终有效,并且内存可以安全释放,而不会积累垃圾。

所有权的基本规则虽然简单易懂,但起初可能难以理解和接受: 每个值都只有一个所有者。同一时间只能有一个所有者;当所有者离开其作用域时,该值将被销毁并释放其内存。例如,这适用于…… String当声明它的代码块执行完毕后,它会自动被调用。 drop 这样就能释放堆内存。

当你给另一个变量赋值或将其按值传递给函数时,该属性会被移动。这意味着 移动后,原始变量不再有效。这种移动语义避免了重复释放,因为永远不会有两个所有者试图释放同一个资源。

为了允许程序中的多个部分访问同一个值而不改变所有权,Rust 引入了引用和借用机制。借用时,你会创建一个引用。 &T (不可变的)或 &mut T (可变)在不转移所有权的情况下改变值。 贷款金额受贷款审核机构规则的限制。它会检查引用是否比它们指向的数据更持久,以及可变访问和共享访问是否危险地混合在一起。

贷款规则可概括如下:在任何特定时间,您要么拥有贷款,要么没有贷款。 多个不可变引用 赋予一个值,或 单个可变引用但两者不能同时进行。这消除了共享内存中的竞态条件:要么有很多读者,要么只有一个写者;绝不会出现同一时刻读者和写者同时访问同一数据的情况。

复合类型:结构体、枚举和智能指针

Rust 提供了多种将相关数据分组到更丰富的结构中的方法,首先是 结构结构体允许您定义具有命名字段的自定义类型,例如具有电子邮件、姓名、活动状态和登录计数器的用户。

要创建结构体实例,需要填写所有字段,并且可以将包含该结构体的变量标记为可变,以便稍后修改其值。此外,还有结构体更新语法,允许您通过重用现有实例中的某些字段来构建新实例。 ..otro_struct.

MGI 枚举 它们是另一个重要的支柱:它们允许您定义一个类型,该类型可以是多个可能的变体之一,每个变体都可以带有或不带有关联数据。一个经典的例子是 IP 地址枚举,它只有一个变体。 V4 它存储四个八位字节和另一个 V6 存储一个包含 IPv6 表示法的字符串。

Rust 的标准库包含两个非常重要的枚举: Option<T> y Result<T, E>第一个表示值的存在与否(有或无),用于避免空指针;第二个表示可以执行的操作。 返回正确结果或错误要求错误处理必须明确且安全。

为了管理动态内存和共享数据,Rust 拥有 智能指针Box<T>将值移至堆并保持唯一所有权; Rc<T>,单线程环境下的共享引用计数;以及 Arc<T>, 如同 Rc 但它对多线程安全。在将动态内存与并发结合使用时,正确使用它们至关重要。

货物和货箱生态系统

Cargo 是将 Rust 生态系统维系在一起的粘合剂: 管理编译、依赖关系和项目生命周期每个项目都有一个文件 Cargo.toml 它充当清单,声明名称、版本、语言版本和外部依赖项。

  修复:Windows 10 内部 PCI 总线驱动程序错误

部分 此文件允许您列出第三方库及其版本。当您运行 cargo build o cargo runCargo 会自动从 crates.io 下载这些 crate,编译它们,并将它们链接到你的项目中。添加随机数生成器、Web 框架或加密库就是这么简单。

最常用的命令包括: cargo new 启动二进制项目 o cargo new --lib 适用于图书馆; cargo build 以调试模式编译; cargo build --release 获得优化的、面向生产的版本; cargo test 运行一系列测试。

cargo check 它值得特别一提:它将代码编译到中间状态,而不生成二进制文件,这使得它 能够非常快速地检测编译错误它非常适合快速迭代,同时借用检查器会指出属性、引用和生命周期方面的问题。

得益于这种生态系统,将项目构建成小型、定义明确的 crate 已成为一种常见的做法,这样可以在 crate 之间共享代码并重用社区创建的解决方案。例如,对于高级并发编程,可以使用 Tokio 这样的 crate 进行异步编程,或者使用 crossbeam 来实现高性能并发数据结构。

Rust 中的并发:线程、互斥锁、通道和原子操作

并发性是 Rust 引起如此广泛关注的原因之一:它允许你利用多核处理器。 避免陷入线程和共享内存的典型错误如果你是第一次接触这些主题,区分几个概念会很有帮助。

并发是指在单个或多个核心上执行多个时间上重叠的任务。在 Rust 中,你可以创建系统线程来并行执行工作,并且语言会引导你确保线程间的数据共享是安全的。一个典型的错误是竞态条件,即两个线程同时访问和修改数据,而结果取决于执行顺序——这种情况非常难以调试。

为了协调对共享数据的访问,Rust 依赖于诸如以下的基本函数: 互斥体它保证了互斥性:一次只能有一个线程进入临界区。结合 Arc<T> 为了在线程之间共享所有权,可以构建符合所有权和借用规则的共享数据结构。

Rust 中大力提倡的另一种常见的线程间通信方式是消息传递。 渠道通道有发送端和接收端;线程通过它传递消息(值),这减少了可变共享内存的使用,并简化了对系统状态的推理。

当你深入研究底层并发时,会出现以下内容: 原子类型原子变量的访问是通过线程层面上不可分割的操作进行的。这使得共享计数器、状态标志、无锁队列等功能的实现成为可能。掌握原子变量需要理解内存模型和访问命令,因此许多开发者倾向于先从互斥锁和通道入手,然后再深入研究这些细节。

学习并发和原子操作的入门知识和资源

如果你没有任何经验就进入这个领域,最明智的做法是…… 建立扎实的通用概念基础 在学习 Rust 的原子类型等高级工具之前,应该先了解一些基础知识。像《Programming Rust》这样的书籍会提供循序渐进的介绍,但专注于原子类型和锁的书籍一开始看起来比较晦涩难懂也是正常的。

为了更方便起见,建议您先熟悉以下内容: 传统线程、互斥和消息传递 在 Rust 中。尝试一些示例。 std::thread, std::sync::Mutex, std::sync::Arc 和渠道 std::sync::mpsc 它能帮助你了解编译器如何引导你以及它避免了哪些错误。

同时,强烈建议复习一下关于并发的入门资源,即使它们并非专注于 Rust:了解什么是竞态条件,什么是阻塞,共享内存与消息传递的区别,以及如何使用锁。 一旦这些概念对你来说变得自然而然,原子物理学就不再是“黑魔法”了。 它们就变成了另一种工具,只不过是一种非常精密的工具。

当你回到 Rust 中关于原子操作和锁的更高级文本时,如果你已经理解了每个构造试图解决的问题,那么理解其中的推理就会容易得多:从简单的线程安全计数器到最大限度减少争用的无锁结构。

归根结底,Rust 既提供了高级原语,也提供了非常底层的工具,关键在于始终选择能够解决你的问题的最安全的抽象级别,并最终实现原子代码。 unsafe 只有当它真正能带来价值,并且你完全理解其含义时,才值得尝试。

类型、所有权、借用、库、工具和并发原语组成的整个生态系统共同提供了一种用于编写代码的语言。 快速、稳定且易于维护的软件这可以最大限度地减少历史上一直困扰系统编程的许多类型的错误。随着你通过小型项目、Rustlings 等练习以及官方文档进行实践,这些概念将从看似严格的规则转变为你的得力助手,在问题影响生产环境之前发出警告。

Rust 语言实例介绍-0
相关文章:
Rust 完整入门:实用入门指南及示例