Rust背景
让我们从Rust语言的背景开始,探索它的起源。Rust最初是Mozilla研究院在2006年的一个个人项目。第一个稳定的公开版本发布于2015年5月,但在此之前Mozilla已经在生产软件中使用了Rust。2021年,Rust基金会成立,其宪章是管理Rust发布路线图和语言治理。多年来,许多大公司(如亚马逊、谷歌、微软、Meta等)采用Rust进行系统和应用程序开发。截至撰写本文时,当前的发布版本是1.74.0。
Rust被认为是一种重要的语言,主要原因是它的执行速度、内存管理/安全性以及并发能力。
与其他语言相比,Rust程序免受以下问题的困扰:
-
悬空指针
-
数据竞争
-
缓冲区溢出
-
迭代器失效
当你开始使用Rust时,我发现最令人沮丧但同时也最有益的能力是,编译器几乎会引导你编写正确的代码。但它非常挑剔。Rust是一种静态且强类型的语言。变量类型必须在编译时已知,试图将一种类型的变量赋值给另一种类型的变量会导致编译错误。编译器会提供尽可能多的信息,甚至是解决方案。
下面是一个尝试将字符串传递给无符号整数变量的人为示例。
不用担心上面的代码,现在只关注以下几行,解释究竟发生了什么。它告诉我们问题出现在第3行,以及具体的问题是什么。我们将在本文的后续部分以及未来的文章中进一步探讨为什么会发生这种类型错误。
go
3 | let j : u32 = i;
| --- --- ^ expected `u32`, found `String`
Rust安装指南
在探索Rust的过程中,我们会发现其工具链非常出色,从安装开始就不例外。
Rustup
Rustup 是安装 Rust 的工具,它允许用户在稳定版、测试版和夜间版编译器构建之间切换,并且可以在每个类别中保持更新到新版本。它在Rust支持的所有平台上运行。我们将探讨提供的文档,Rustup 有自己的书籍可以在这里找到。
访问 https://rustup.rs/,它会尝试检测您的操作系统并展示安装Rust的方法。在我的Mac上,它检测到Unix系统并提供了curl安装方法。但使用Homebrew安装会更简单。
go
brew install rustup
安装完成后,运行以下命令来初始化您的Rust环境。
go
rustup-init
然后通过运行以下命令来验证。
go
rustc --version
您可以通过运行以下命令来确保拥有最新版本。
go
rustup upgrade
如果有最新版本可用,它将安装该版本。Rust的升级周期相对较频繁,因此您可能需要比使用其他语言时更频繁地运行此命令。
如果您想知道像rustc、cargo和rustup这样的工具安装在哪里,请查看 $HOME/.cargo/bin 目录。
Visual Studio Code
虽然有几种IDE可用于Rust,但我发现Visual Studio Code非常好用。假设您已经使用rustup安装了Rust,并且安装了Visual Studio Code,您会想安装"rust-analyzer"扩展。我还安装了"rust"扩展。
在Mac上,您还需要运行以下命令来启用从命令行启动VS Code。在VS Code中通过运行以下命令来完成此操作。
通过⌘⇧P打开命令面板,并键入shell command来找到Shell Command:
在执行"Install 'code' command in PATH"命令之前,先使用"Uninstall 'code' command in PATH"命令。
执行该命令后,您就可以在任何文件夹中简单地键入 code . 来打开VS Code并开始编辑该文件夹中的文件。
Rust Playground
在Rust的入门讨论中,如果不提到这个对语言学习者来说神奇的工具,那就不完整了。如果你想尝试运行Rust代码,却不想安装它,或者你想快速测试一些代码,可以使用Rust Playground:
https://play.rust-lang.org/。这是一个功能齐全的Rust环境,允许你交互、调试甚至分享你的Rust代码。
Rust Playground拥有很多优秀的功能。这包括访问用于格式化或审查Rust代码的工具(我们将在下面探讨这些)。如你所见,它甚至能轻松地与他人分享代码,比如,通过点击这个链接:
Cargo
Cargo是Rust的构建系统和包管理器。Cargo为你处理许多任务,如构建代码、下载代码依赖的库以及构建这些依赖。我发现使用Cargo是管理Rust项目最简单的方式。
go
% cargo --version
cargo 1.74.0 (ecb9851af 2023-10-18)
这是我当前系统上的结果,因为我正在运行Rust 1.74.0。
Cargo允许高效管理新项目,让我向你展示我觉得有用的流程。
go
cargo new hello_cargo
cd hello_cargo
code .
这将创建一个带有依赖管理文件、git文件的新项目,切换到那个目录,然后用VS Code打开那个文件夹。
创建的依赖管理文件名为Cargo.toml。toml是Cargo的配置格式。
go
[package]
name = "hello_cargo"
version = "0.1.0"
edition = "2021"
# 详细信息请见 https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
Cargo希望你的源文件位于src目录内。
如果你通过其他方式开始一个项目,可以通过确保项目代码在src目录内,并创建一个适当的Cargo.toml文件(如上所示),轻松将其转换为使用Cargo的项目。
常用的Cargo命令包括:
go
# 清理项目的构建产物
cargo clean
# 检查是否能编译,但不进行构建
cargo check
# 构建项目
cargo build
# 构建不带调试信息的发布版本
cargo build --release
# 运行项目
cargo run
# 打开包含项目文档的页面(包括任何///注释)
cargo doc --open
# 自动修复代码问题
cargo fix
Crate(箱)
在Rust中,crate是编译器一次考虑的最小代码单元。crate有两种形式:二进制crate和库crate。二进制crate是你可以编译成可执行文件的程序,比如命令行程序或服务器。这些程序有一个叫做main的函数。Crate可以包含模块,这些模块可能在其他文件中定义。
库crate没有main函数,它们不编译成可执行文件。相反,它们定义的功能旨在与多个项目共享。
包(package)是一个或多个crate的捆绑,提供一组功能。包含一个Cargo.toml文件,描述如何构建这些crate。
在本系列后续文章中,我们将看到如何将应用程序分割成不同的crate、包和模块的示例。
工具
rustfmt
rustfmt是一个工具,用于根据社区代码风格指南格式化Rust代码。它是非常可配置的,你可以创建一个名为rustfmt.toml的文件,并设置这里找到的任何参数来格式化你的代码。
直接运行:
go
rustfmt main.rs
由于我发现Cargo是所有命令行工具的首选选项,我只需简单运行:
go
cargo fmt
注意,rustfmt也是Rust Playground中TOOLS下的一个选项。
clippy
Clippy工具是一组静态代码分析规则的集合,用于标记编程错误、bug、风格错误和可疑结构。Clippy用于分析你的代码,以便你可以捕捉常见错误并改进你的Rust代码。
它可以通过输入以下命令简单运行:
go
cargo clippy
注意,clippy也是Rust Playground中TOOLS下的一个选项。
Primitive Types
在本文的最后一部分,我们将探讨Rust支持的原始类型(primitive types)。
Rust中最简单的类型被称为原始类型。这些包括数字、字符和字符串。我们将从整数类型开始,即没有小数点的整数。整数类型分为有符号和无符号两种。
有符号整数类型以字母'i'开头,大小范围从8到128,如下所示。
-
i8
-
i16
-
i32
-
i64
-
i128
无符号整数类型以字母'u'开头,大小范围同样从8到128,如下所示。
-
u8
-
u16
-
u32
-
u64
-
u128
Rust使用usize作为索引的大小。usize是用于索引的最佳大小,因为索引不能为负数,允许在大型索引空间中灵活使用,并且需要适应32位计算机。
只要源变量中的内容能够适应目标变量类型的最大值,你就可以在不同整数类型之间进行赋值。下面是一个例子。如果将第二行改为128,则第四行将失败,因为i8类型的最大值是127,而我们声明它为i8。
go
fn main() {
let mut i = 127;
let j: i8 = 12;
i = j;
println!("{}", i)
}
我们将在后面讨论mut关键字的使用,但现在只需知道它允许在声明后对i进行赋值。默认情况下,变量是不可变的(不允许被更改)。
如果未声明类型,则整数的默认大小为i32,所以上面的变量i将是i32类型。
浮点数是带有小数点的数字。10.5是一个浮点数,而2是一个整数。就像整数一样,它们由诸如f32或f64之类的类型定义。也像整数一样,如果环境支持,f64是默认的浮点数类型。
字符(char)类型始终使用4个字节。但字符串不同,单个字符不总是使用4个字节。当字符是字符串的一部分(而不是char类型)时,字符串被编码为使用每个字符所需的最少内存量。
字符的大小和字符串的变化如下面的代码片段所示。
go
use std::time::{Instant};
fn main() {
let now = Instant::now();
println!("Size of a char: {}", std::mem::size_of::<char>());
println!("Size of a: {}", "a".len());
println!("Size of ñ: {}", "ñ".len());
println!("Size of 国: {}", "国".len());
println!("Size of : {}", "".len());
let now2 = Instant::now();
println!("==> {} ms", now2.duration_since(now).as_micros());
}
结果如下。请注意,还添加了一些代码来显示微秒级的运行时长。
go
Size of a char: 4
Size of a: 1
Size of ñ: 2
Size of 国: 3
Size of : 4
==> 6773 ms
请注意,.len()方法返回的是字节数,而不是字母数。
上面的例子展示了如何从Instant库中包含一个标准库函数,now()。这是在顶部使用use关键字包含的。
每种类型的详细信息可以在这里找到。
总结
这篇文章为学习Rust语言打下了基础。我们探讨了安装Rust、运行程序的替代方案、基本工具以及Rust中可用的原始数据类型的概述。在下一节中,我们将探讨内存管理的重要话题,以及借用(borrowing)、遮蔽(shadowing)、所有权(ownership)以及不可变性等概念。理解这些概念,其中一些是Rust独有的,对于精通这种语言至关重要。
Rust的内存管理是其核心特性之一,不同于传统语言如C或C++中的手动内存管理,Rust通过所有权系统自动处理内存,旨在减少内存泄漏和其他常见错误。在Rust中,每一个值都有一个被称为其"所有者"的变量,值在任何时候都只能有一个所有者。当所有者离开作用域时,值将被丢弃。
借用是Rust中另一个重要概念。它允许我们创建指向数据的引用,但不取得所有权。这意味着数据可以被多个部分的代码安全地访问,而不会引起数据竞争或其他问题。
遮蔽则允许我们重新使用变量名。这意味着你可以在同一个作用域内声明一个新的同名变量,新变量会"遮蔽"掉旧变量。
不可变性是Rust的另一个核心概念。在Rust中,默认情况下,所有变量都是不可变的。这增加了代码的安全性和清晰性。当然,如果需要,你可以通过使用mut关键字明确指定可变性。
通过了解和掌握这些概念,你将能够更有效地编写Rust代码,并充分利用Rust提供的安全和性能优势。
在我们的下一篇文章中,我们将深入探讨这些概念,并通过实际示例来展示它们在Rust编程中的应用。我们还将探讨Rust的错误处理机制和模式匹配,这是Rust提供的另一组强大功能。