建设网站英语,成免费crm破解版,制作网站需要多少费用,官方网站建设公上一节#xff1a;【Rust 易学教程】学前准备#xff1a;Cargo#xff0c; 你好
今天#xff0c;我们正式进入 Rust 基础的学习。在本文的内容中#xff0c;我会为大家介绍以下内容#xff1a;
基本 Rust 语法: 变量、标量和复合类型、枚举、结构、引用、函数和方法。控…上一节【Rust 易学教程】学前准备Cargo 你好
今天我们正式进入 Rust 基础的学习。在本文的内容中我会为大家介绍以下内容
基本 Rust 语法: 变量、标量和复合类型、枚举、结构、引用、函数和方法。控制流结构: if、if let、while、while let、break 和 continue。模式匹配: 解构枚举、结构体和数组。
Rust 是个啥
Rust 是一种新的编程语言在 2015 年发布了 1.0 版本我会从以下方面让你知道 Rust 出现的意义
Rust 是一种静态编译语言其作用与 c 类似。rustc 使用 LLVM作为它的编译框架关于 LLVM 的具体了解可以访问其官方网址https://llvm.org/。Rust 支持许多平台和架构。例如 x86, ARM, WebAssembly, …等架构以及 Linux, Mac, Windows, …等平台。Rust 被用于广泛的设备如 固件和引导加载的启动程序、智能显示设备、移动电话、桌面、服务器等等。
我们发现Rust 有与 c 相同的特性:
高的灵活性。高度控制。可以缩小到非常受限的设备如微控制器。没有运行时或垃圾收集。在不牺牲性能的前提下注重可靠性和安全性。
HiI am Rust
了解了 Rust 是什么后就让我们先来体验一番 Rust 最简单的程序
fn main() {println!(Hi, I am Rust!);
}从上面的代码中我们看到 rust 代码具有如下一些特征
函数由 fn 引入。像 C 和 c 一样块由花括号分隔。main 函数是程序的入口点。Rust 有卫生宏(hygienic macros)println! 就是它的一个例子。Rust 字符串是 UTF-8 编码的可以包含任何 Unicode 字符。 什么是 卫生宏卫生宏和普通宏的区别有点类似词法作用域函数和动态作用域函数的区别。比如宏调用处有个名字 name1同时宏内部也有一个名字 name1那么卫生宏展开的时候就会把自己内部的 name1 改名成 name2普通宏则不改名“捕捉”外部的名字。 为了方便你理解我在这里再小结一下上面的内容 Rust 非常像其他遵循 C/ c /Java 范式的传统语言。 Rust 是现代的完全支持 Unicode 之类的东西。 Rust 在需要可变数量的参数(不允许函数重载)的情况下使用宏。 宏是“卫生的”意味着它们不会意外地从它们所使用的范围中捕获标识符。Rust 宏实际上只是部分卫生的。 Rust 是多范式的。例如它具有强大的面向对象编程特性而且虽然它不是函数式语言但它包含了一系列函数式概念。
根据上面的小结你是否也能发现 Rust 的一些独特卖点:
编译时内存安全。例如Rust 通过借用检查器消除了整个类的运行时错误得到了像 C和 c 一样的性能但没有内存不安全的问题。此外还可以获得具有模式匹配和内置依赖项管理等结构的现代语言。缺少未定义的运行时行为。现代语言的特点。例如可以获得像 C和c 那样快速且可预测的性能(没有垃圾收集器)以及访问低级硬件。
为什么是 Rust
接下来我会为你从几个方面介绍为什么 Rust 会在众多语言中突出重围。先来一个示例。
示例C 语言是怎么做的
首先我们来看一个 C 语言的例子
#include stdio.h
#include stdlib.h
#include sys/stat.hint main(int argc, char* argv[]) {char *buf, *filename;FILE *fp;size_t bytes, len;struct stat st;switch (argc) {case 1:printf(Too few arguments!\n);return 1;case 2:filename argv[argc];stat(filename, st);len st.st_size;buf (char*)malloc(len);if (!buf)printf(malloc failed!\n, len);return 1;fp fopen(filename, rb);bytes fread(buf, 1, len, fp);if (bytes st.st_size)printf(%s, buf);elseprintf(fread failed!\n);case 3:printf(Too many arguments!\n);return 1;}return 0;
}
上面的代码中你发现了多少 bug?
尽管只有29行代码但这个 C 语言示例中至少有 11 行包含了严重的错误:
赋值而不是相等比较(第28行)printf 的多余参数(第23行)文件描述符泄漏(在第26行之后)多行 if 中忘记使用大括号(第22行)在 switch 语句中忘记了中断(第32行)忘记了 buf 字符串的 null 终止导致缓冲区溢出(第29行)不释放 malloc 分配的缓冲区导致内存泄漏(第21行)越界访问(第17行)未检查 switch 语句中的情况(第11行)未检查stat 和 fopen 的返回值(第18行和第26行)
即使对于 C 编译器这些错误也不应该很明显吗? 不令人惊讶的是即使在最新的GCC版本(撰写本文时为13.2)中该代码也会在默认警告级别下编译无警告。
这不是一个非常不现实的例子吗? 绝对不是这类错误在过去会导致严重的安全漏洞。例如
赋值代替相等比较: 2003年 Linux 后门尝试漏洞忘记在多行 if 中使用大括号: Apple的 goto fail 漏洞switch 语句中被遗忘的中断: 中断 sudo 的中断
那么你可能会问 Rust 在这里又能好到哪里去呢? —— Safe Rust 使所有这些 bug 都不可能出现例如以下:
不支持if子句中的赋值。格式字符串在编译时进行检查。资源通过 Drop 特性在作用域结束时被释放。所有 if 子句都需要大括号。match(在Rust中相当于switch) 不会失败因此开发者不会不小心忘记了 break。缓冲区切片携带它们的大小不依赖于 NULL 终止符。当相应的 Box 离开作用域时通过 Drop 特性释放堆分配的内存。越界访问会导致 panic或者可以通过切片的 get 方法进行检查。match 会要求所有 case 都要得到处理。易出错的 Rust 函数返回的 Result 值需要拆封从而检查是否成功。此外如果没有检查带有 #[must_use]标记的函数的返回值编译器会发出警告。
编译时验证
编译时的静态内存会进行如下验证:
验证没有未初始化的变量。验证没有内存泄漏。验证没有 double-frees。验证 use-after-free。验证 NULL 指针。验证忘记锁定的互斥锁。验证线程之间没有数据竞争。验证迭代器是否失效。
运行时验证
以下行为将会判定为是在运行时无未定义的行为:
检查数组访问的边界。定义了整数溢出(panic 或 wrap-around)。 整数溢出是通过编译时溢出检查标志定义的。如果启用程序将陷入奔溃否则开发者将获得环绕语义。默认情况下将在调试模式cargo build和发布模式(cargo build --release)中获得 panic。 不能使用编译器标志禁用边界检查。它也不能直接使用不安全关键字禁用。但是不安全允许开发者调用诸如slice::get_unchecked 之类的函数这些函数不进行边界检查。 Rust 具备现代语言的特性
Rust 是用过去几十年积累的所有经验构建起来的汲取几大语言的精华又进行了改进。在语言特性上它具备以下几点
枚举和模式匹配。泛型。没有额外的 FFI。零成本抽象。
在工具支持上具备以下几点
良好的编译器错误检测。内置依赖项管理器。内置测试的支持。优秀的语言服务器协议支持。
往更细的说主要是以下几点 零成本抽象类似于c意味着你不必为使用内存或 CPU 的高级编程结构“付费”。例如使用 For 编写循环应该产生与使用.iter().fold() 结构大致相同的低级指令。 值得一提的是Rust 枚举是“代数数据类型”也被称为“和类型”它允许类型系统表达像OptionT和ResultT, E这样的东西。 提醒开发者关注错误——许多开发者已经习惯忽略冗长的编译器输出。Rust 编译器明显比其他编译器更健谈。它通常会为开发者提供可操作的反馈准备复制粘贴到你的代码中。 与Java、Python和Go等语言相比Rust 标准库很小。Rust 没有提供一些你可能认为是标准和必要的东西例如 一个随机数生成器但开发者请参阅 rand。 支持SSL或TLS但开发者请参阅 rusttls。 对JSON的支持开发者可以参阅 serde_json。这背后的原因是标准库中的功能不能消失所以它必须非常稳定。对于上面的例子Rust 社区仍在努力寻找最佳解决方案——也许对于其中的一些事情没有单一的“最佳解决方案”。
Rust 附带了 Cargo 形式的内置包管理器这使得下载和编译第三方 crate 变得非常简单。这样做的结果是标准库可以更小。https://lib.rs 这个网站可以帮助你找到更多第三方库。
rust-analyzer 对主要的 ide 和文本编辑器实现了支持。
基础语法
大部分 Rust 语法对于 C、c或Java 来说都很熟悉。例如
块和作用域由花括号分隔。行注释以//开头块注释以/*…* /。像if和while这样的关键词的工作原理是一样的。变量赋值用完成比较用完成。
标量类型
类型示例有符号整数i8, i16, i32, i64, i128, isize-10, 0, 1_000, 123_i64无符号整数u8, u16, u32, u64, u128, usize0, 123, 10_u16浮点数f32, f643.14, -10.0e20, 2_f32字符串str“foo”, “two\nlines”Unicode标量值char‘a’, ‘α’, ‘∞’布尔型booltrue, false
上方表格中数字中的所有下划线都可以省略它们只是为了便于阅读。因此1_000可以写成1000(或10_00)123_i64可以写成123i64。
同时上面的类型的宽度如下:
iN, uN 和 fN 是 N 位宽Isize 和 usize是指针的宽度Char 是 32 位宽Bool 是 8 位宽。
除此之外原始字符串允许开发者创建一个转义值如: r\n \\n。你可以嵌入双引号在引号的两边加上等量的#:
fn main() {println!(r#a hreflink.htmllink/a#);println!(a href\link.html\link/a);
}字节(Byte)字符串允许你直接创建[u8]值:
fn main() {println!({:?}, babc);println!({:?}, [97, 98, 99]);
}复合类型
类型示例数组[T; N][20, 30, 40], [0; 3]元组(), (T,), (T1, T2), …(), (‘x’,), (‘x’, 1.2), …
数组的赋值和访问:
fn main() {let mut a: [i8; 10] [42; 10];a[5] 0;println!(a: {a:?});
}数组类型 [T;N] 保存了 N 个(编译时常量)相同类型 t 的元素。注意数组的长度是其类型的一部分这意味着 [u8;3] 和 [u8;4] 被认为是两种不同的类型。可以使用字面量给数组赋值。添加 #例如{a:#?}可以有“漂亮的输出”格式这样更容易阅读。
元组的赋值和访问:
fn main() {let t: (i8, bool) (7, true);println!(t.0: {}, t.0);println!(t.1: {}, t.1);
}与数组一样元组也有固定的长度。 元组将不同类型的值组合成一个复合类型。 元组的字段可以通过周期和值的索引来访问例如 t.0, t.1。 空元组 () 也被称为“单元类型”。它既是一个类型又是该类型的唯一有效值——也就是说该类型及其值都表示为 ()。例如它用于表示函数或表达式时没有返回值。
引用类型
和c一样Rust 也有引用类型:
fn main() {let mut x: i32 10;let ref_x: mut i32 mut x;*ref_x 20;println!(x: {x});
}需要注意的是
赋值时必须解除对 ref_x 的引用类似于 C 和 c 指针。Rust 在某些情况下会自动解除引用特别是在调用方法时(如 ref_x.count_ones())。声明为 mut 的引用可以在其生命周期内绑定到不同的值。一定要注意 let mut ref_x: i32 和 let ref_x: mut i32 之间的区别。第一个表示可以绑定到不同值的可变引用而第二个表示对可变值的引用。
悬垂引用
Rust 将静态地禁止悬垂引用:
fn main() {let ref_x: i32;{let x: i32 10;ref_x x;}println!(ref_x: {ref_x});
}引用你可以想象为为“借用”它所引用的值。Rust 正在跟踪所有引用的生命周期以确保它们活得足够长。
Slices 切片
切片为开发者提供了更大集合的视图:
fn main() {let mut a: [i32; 6] [10, 20, 30, 40, 50, 60];println!(a: {a:?});let s: [i32] a[2..4];println!(s: {s:?});
}上述代码中我们通过借用 a 并在括号中指定起始和结束索引来创建切片。 如果切片从索引0开始Rust的范围语法允许我们删除起始索引这意味着a[0.. .len()]和a[.. .. len()]是相同的。 对于最后一个索引也是如此所以a a[2.. .len()]和a a[2..]都是一样的。
因此为了方便地创建整个数组的切片我们可以使用a[…]。
S是对i32s切片的引用。注意s ([i32])的类型不再提到数组长度。这允许我们对不同大小的切片执行计算。
切片总是从另一个对象借用。在本例中a 必须保持“活动”(在作用域中)至少与我们的切片一样长。
String Vs Str
现在我们可以理解Rust中的两种字符串类型:
fn main() {let s1: str World;println!(s1: {s1});let mut s2: String String::from(Hello );println!(s2: {s2});s2.push_str(s1);println!(s2: {s2});let s3: str s2[6..];println!(s3: {s3});
}str: 对字符串切片的不可变引用 String: 可变字符串缓冲区 str 引入了一个字符串切片它是对存储在内存块中的UTF-8编码字符串数据的不可变引用。字符串字面值( Hello )存储在程序的二进制文件中。 Rust 的 String 类型是一个字节向量的包装器。与VecT一样它是私有的。 与许多其他类型一样String::from() 从字符串字面值创建字符串。String::new() 创建一个新的空字符串可以使用push()和push_str()方法向其添加字符串数据。 宏是一种从动态值生成私有字符串的方便方法。它接受与 println!() 相同的格式规范。 你可以通过 和可选的范围选择从 String 中借用 str 切片。 对于c程序员: 你可以将 str 看作 c 中的 const char*但它总是指向内存中的有效字符串。Rust String 大致相当于c中的std:: String(主要区别:它只能包含UTF-8编码的字节并且永远不会使用小字符串优化)。
Functions
Methods
方法是与类型相关联的函数。方法的self参数是它所关联类型的一个实例:
struct Rectangle {width: u32,height: u32,
}impl Rectangle {fn area(self) - u32 {self.width * self.height}fn inc_width(mut self, delta: u32) {self.width delta;}
}fn main() {let mut rect Rectangle { width: 10, height: 5 };println!(old area: {}, rect.area());rect.inc_width(5);println!(new area: {}, rect.area());
}添加一个名为Rectangle::new的静态方法并从 main 调用它:
fn new(width: u32, height: u32) - Rectangle {Rectangle { width, height }
}虽然从技术上讲Rust没有自定义构造函数但静态方法通常用于初始化结构。实际的构造函数Rectangle {width, height}可以直接调用。 添加 Rectangle::square(width: u32) 构造函数来说明此类静态方法可以接受任意参数。
函数重载
不支持重载: 每个函数有一个单独的实现: 总是有固定数量的参数。 总是接受一组参数类型。 不支持默认值: 所有调用站点都具有相同数量的参数。 有时使用宏作为替代方法。
然而函数参数可以是泛型的:
fn pick_oneT(a: T, b: T) - T {if std::process::id() % 2 0 { a } else { b }
}fn main() {println!(coin toss: {}, pick_one(heads, tails));println!(cash prize: {}, pick_one(500, 1000));
}当使用泛型时标准库的Into可以在参数类型上提供一种有限的多态性。这一点我将在后面的小节中介绍更多细节。