哪里帮做企业网站,网站开发适配,中国式现代化是自主产权,如何做链接前言
笔记的内容主要参考与《Rust 程序设计语言》#xff0c;一些也参考了《通过例子学 Rust》和《Rust语言圣经》。
Rust学习笔记分为上中下#xff0c;其它两个地址在Rust学习笔记#xff08;中#xff09;和Rust学习笔记#xff08;下#xff09;。
编译与运行
Ru…前言
笔记的内容主要参考与《Rust 程序设计语言》一些也参考了《通过例子学 Rust》和《Rust语言圣经》。
Rust学习笔记分为上中下其它两个地址在Rust学习笔记中和Rust学习笔记下。
编译与运行
Rustupupdater
它是一个管理 Rust 版本和相关工具的命令行工具。
Rustccompiler
Rust 是一种预编译静态类型ahead-of-time compiled语言这意味着你可以将编译程序生成的可执行文件送给其他人他们甚至不需要安装 Rust 就可以运行。而像其它动态语言Ruby、Python 或 JavaScript则做不到需要分别安装对应的环境这一切都是语言设计上的权衡取舍。
# 编译成功后Rust会输出一个二进制的可执行文件main。
$ rustc main.rs
# 运行
$ ./mainCargopackage manager
仅仅使用 rustc 编译简单程序是没问题的不过随着项目的增长你可能需要管理你项目的方方面面并让代码易于分享。这需要用到Cargo。Cargo 是 Rust 的构建系统和包管理器。
$ cargo new hello_cargo
$ cd hello_cargo
\hello_cargo
|-- src|-- main.rs
|-- Cargo.toml 对于 cargo new 命令它会自动添加 Git 进行版本管理如不想使用可以添加 -- vcsversion control system进行改变例如 cargo new hello_cargo --vcs none
在 Cargo.toml 文件中使用 TOMLTom’s Obvious, Minimal Language格式编写。第一行[package]是一个片段section标题表明下面的语句用来配置一个包。随着我们在这个文件增加更多的信息还将增加其他片段section。在 [package] 中包含了项目的名称、版本、作者以及要使用的 Rust 版本。最后一行[dependencies]是用来罗列项目依赖在 Rust 中代码包被称为 crates。也可通过命令的方式安装 cargo add xxx它会默认从 Crates.io提供 Rust 的各种依赖安装最新版。也可使用 cargo update 来更更新依赖的版本。
[package]
name hello_cargo
version 0.1.0
authors [Your Name youexample.com]
edition 2018[dependencies]Cargo 生成项目的区别是 Cargo 将代码放在 src 目录同时项目根目录包含一个 Cargo.toml 配置文件。Cargo 期望源文件存放在 src 目录中。项目根目录只存放 README、license 信息、配置文件和其他跟代码无关的文件。
使用Cargo编译与运行用到了如下命令
# 这个命令会创建一个可执行文件target/debug/hello_cargo可以通过./target/debug/hello_cargo运行
$ cargo build
# 更简单的运行方式是使用下面的语句
$ cargo run
# 该命令快速检查代码确保其可以编译但并不产生可执行文件
$ cargo check当使用 cargo build 时会在项目根目录创建一个新文件Cargo.lock 这个文件记录项目依赖精确版本信息。
通常 cargo check 要比 cargo build 快得多因为它省略了生成可执行文件的步骤。如果你在编写代码时持续的进行检查cargo check 会加速开发。
最后当项目最终准备好发布时可以使用 cargo build --release 来优化编译项目。这会在 target/release 而不是 target/debug 下生成可执行文件。这些优化可以让 Rust 代码运行的更快不过启用这些优化也需要消耗更长的编译时间。
为什么需要 Cargo.lock
Cargo.lock 可以确保构建是可重现的在第一次运行 cargo build 时创建Cargo 会计算出所有符合要求的依赖版本包含间接依赖文件写入该文件。这样在大型项目中能够锁定整个项目依赖树的精确版本这包括所有的直接和间接依赖因为可能有多个依赖项共享相同的间接依赖或不同的间接依赖。总之就是避免冲突保证重现。
基本语法
变量与常量Variable and constant
// 直接使用let声明类型可以不写Rust会自动识别。对于整型这种会取为默认i32只有明显大于或小于i32范围才会换为别的
let word abc;
let price: i32 199;
// mutmutable声明变量是可变的否则变量无法更改
let mut checked true;
// 常量声明这里的下划线可有可无只是提升可读性
const MAX_POINTS: u32 100_000;
// 覆盖原值
let word def不可变变量与常量的区别
常量不能使用 mut 。常量必须标明类型。常量可以在任何作用域中声明包括全局作用域变量不可以。常量只能被设置为常量表达式而不能是函数调用的结果或任何其他只能在运行时计算出的值可以 1 1 不可以 1 x 。
隐藏Shadowing
可以重新使用 let 声明存在的值会将其覆盖。
数据类型data type 标量scalar 整型integer有符号整数类型以 isigned开头无符号以 uunsigned开头。默认 i32。以有符号为例分为了 i81字节、i162字节、i324字节、i648字节、i12816字节。1字节 8位根据这个可算出其范围。还有一个类型为 isize类型依赖运行程序的计算机架构64 位架构上它们是 64 位的 32 位架构上它们是 32 位的。浮点型floating-point numbersRust 的浮点数类型是 f32 和 f64分别占 32 位和 64 位。默认类型是 f64因为在现代 CPU 中它与 f32 速度几乎一样不过精度更高。声明时要像这样 let x 2.0;。布尔型booltrue 和 false。字符类型character为char像 let c z大小为四个字节four bytes代表了一个 Unicode 标量值Unicode Scalar Value这意味着它可以比 ASCII 表示更多内容。 复合compound 元组tuple可以将多个其他类型的值组合进一个复合类型方式。元组长度固定一旦声明其长度不会增大或缩小。 // 也可不写类型
let tup: (i32, f64, u8) (500, 6.4, 1);
// 访问
// 方式1使用模式匹配pattern matching来解构destructure元组值
let (x, y, z) tup;
println!(The value of y is: {}, y);
// 方式2点号加索引
println!(The value of x is: {}, tup.0);对于元组还有另一个用处接函数值。 fn main() {let s1 String::from(hello);let (s2, len) calculate_length(s1);println!(The length of {} is {}., s2, len);
}fn calculate_length(s: String) - (String, usize) {let length s.len();(s, length)
}数组array可以包含多个相同类型的值Rust 中的数组与一些其他语言中的数组不同因为 Rust 中的数组是固定长度的一旦声明它们的长度不能增长或缩小。数组并不如 vector 类型灵活vector 类型是标准库提供的一个允许增长和缩小长度的类似数组的集合类型。 // 声明
let months [January, February, March, April, May, June, July,August, September, October, November, December];
// 也可以加上类型和长度
let a: [i32; 5] [1, 2, 3, 4, 5];
// 可以创多个重复值的数组5个3
let a [3; 5];
// 访问
let first a[0];整型溢出integer overflow
比方说有一个 u8它可以存放从零到 255 的值。那么当你将其修改为 256 时会发生什么呢
当在 debug 模式编译时会使程序 panic在 release 构建中Rust 不检测溢出256 会变成 0257 变成 1依此类推。
数值运算
不同类型的整数之间可以随意加减乘除小心溢出但对于浮点数运算时每个值的类型都必须相同。
无效的数组元素访问
一般程序语言数组越界访问可能导致无效内存访问这可能会引起程序崩溃或安全漏洞如缓冲区溢出。与之相比Rust 在运行时执行边界检查当尝试访问超出数组长度的元素时程序会 panic 并安全退出。这种行为防止了潜在的无效内存访问提高了程序的整体安全性。
函数
基本格式
fn another_function(x: i32y: i32) - i32 {...
}语句Statements和表达式Expressions
// 语句
let y 6;
// 会报错不能把let语句给x
let x (let y 6);
// { let x 3; x 1}是一个表达式
let y {let x 3;x 1
};
// 表达式直接返回不能加分号。如果想加可以用return;
fn plus_one(x: i32) - i32 {x 1
}两种函数返回值的方式
fn plus_one(x: i32) - i32 {// 第一种 2//第二种return 2
}控制流
判断
fn main() {let number 6;if number % 4 0 {println!(number is divisible by 4);} else if number % 3 0 {println!(number is divisible by 3);} else if number % 2 0 {println!(number is divisible by 2);} else {println!(number is not divisible by 4, 3, or 2);}
}
// 不能这样
let number 3;
if number {println!(number was three);
}
// let if的用法和if let不一样
let condition true;
let number if condition {5
} else {// 上下两个值类型需相同。Rust需要在编译时就确切的知道number变量的类型这样它就可以在编译时验证在每处使用的number变量的类// 型是否有效。Rust并不能够在number的类型只能在运行时确定的情况下工作这样会使编译器变得更复杂而且只能为代码提供更少的保// 障因为它不得不记录所有变量的多种可能的类型。6
};循环
// loop
loop {println!(again!);
}
// 这里为什么可以因为Rust的break不太一样break counter * 2像是return counter * 2
let result loop {counter 1;if counter 10 {break counter * 2;}
};
// while
while number ! 0 {println!({}!, number);number number - 1;
}
// for
let a [10, 20, 30, 40, 50];
for element in a {println!(the value is: {}, element);
}
for number in (1..4).rev() { // rev用来反转rangeprintln!({}!, number);
}所有权ownership
Rust 的核心功能之一就是所有权。所有运行的程序都必须管理其使用计算机内存的方式一些语言中具有垃圾回收机制在程序运行时不断地寻找不再使用的内存在另一些语言中程序员必须亲自分配和释放内存Rust 则选择了第三种方式通过所有权系统管理内存编译器在编译时会根据一系列的规则进行检查。在运行时所有权系统的任何功能都不会减慢程序。
栈Stack和堆Heap
在很多语言中你并不需要经常考虑到栈与堆。不过在像 Rust 这样的系统编程语言中值是位于栈上还是堆上在更大程度上影响了语言的行为以及为何必须做出这样的抉择。
栈和堆都是代码在运行时可供使用的内存但是它们的结构不同。栈以放入值的顺序存储值并以相反顺序取出值这也被称作后进先出last in, first out。栈中的所有数据都必须占用已知且固定的大小。在编译时大小未知或大小可能变化的数据要改为存储在堆上。堆是缺乏组织的当向堆放入数据时你要请求一定大小的空间。操作系统在堆的某处找到一块足够大的空位把它标记为已使用并返回一个表示该位置地址的指针pointer。这个过程称作在堆上分配内存allocating on the heap。将数据推入栈中并不被认为是分配。
入栈比在堆上分配内存要快因为入栈时操作系统无需为存储新数据去搜索内存空间其位置总是在栈顶。相比之下在堆上分配内存则需要更多的工作这是因为操作系统必须首先找到一块足够存放数据的内存空间并接着做一些记录为下一次分配做准备。
在访问时堆上的数据比访问栈上的数据慢因为必须通过指针来访问。现代处理器在内存中跳转越少就越快。对于栈当你的代码调用一个函数时传递给函数的值包括可能指向堆上数据的指针和函数的局部变量被压入栈中。当函数结束时这些值被移出栈。
跟踪哪部分代码正在使用堆上的哪些数据最大限度的减少堆上的重复数据的数量以及清理堆上不再使用的数据确保不会耗尽空间这些问题正是所有权系统要处理的。一旦理解了所有权你就不需要经常考虑栈和堆了不过明白了所有权的存在就是为了管理堆数据能够帮助解释为什么所有权要以这种方式工作。
函数与栈
我刚开始特别纠结栈不是 LIFO 吗那我先存入一个变量在存入另一个那我想用第一个怎么用 这里栈的应用不是我想象的呢么简单涉及汇编比较底层简单解释一下这里存入栈的是各种指令。像对于这种拿变量都是直接通过什么指令给到对方地址当函数结束这些指令也会出栈。
所有权规则
Rust 中的每一个值都有一个被称为其所有者owner的变量。值有且只有一个所有者。当所有者变量离开作用域这个值将被丢弃。
变量的作用域scope
在有垃圾回收garbage collector**GC的语言中 GC 记录并清除不再使用的内存而我们并不需要关心它。没有 GC 的话识别出不再使用的内存并调用代码显式释放就是我们的责任了跟请求内存的时候一样。从历史的角度上说正确处理内存回收曾经是一个困难的编程问题。如果忘记回收了会浪费内存。如果过早回收了将会出现无效变量。如果重复回收这也是个 bug。我们需要精确的为一个 allocate 配对一个 free。Rust 采取了一个不同的策略内存在拥有它的变量离开作用域后就被自动释放。
{ // s 在这里无效, 它尚未声明let s hello; // 从此处起s 是有效的// 使用 s
} // 此作用域已结束s 不再有效内存与分配Memory and allocation 变量与数据交互的方式一移动move // 正确
let x 5;
let y x;
println!({}, x)
// 错误
let s1 String::from(hello);
let s2 s1;
println!({}, s1);首先对于像整形这样的在编译时已知大小的类型被整个存储在栈上所以拷贝其实际的值是快速的意味着没有理由在创建变量 y 后使 x 无效。Rust 有一个叫做 Copy trait 的特殊注解可以用在类似整型这样的存储在栈上的类型上。如果一个类型拥有 Copy trait一个旧的变量在将其赋值给其他变量后仍然可用。 而对于像 String 这样在堆上的值Rust 不像别的语言可能有浅拷贝和深拷贝的说法会直接让第一个值无效这个操作被称为移动move。Rust 永远也不会自动创建数据的 “深拷贝”。因此任何自动的复制可以被认为对运行时性能影响较小。 变量与数据交互的方式二克隆clone 如果确实需要深度复制 String 中堆上的数据可以使用 clone来操作。 let s1 String::from(hello);
let s2 s1.clone();
println!(s1 {}, s2 {}, s1, s2);所有权与函数
这里要注意的是像整形这种值赋给函数后还能用。
fn main() {let s String::from(hello); // s 进入作用域takes_ownership(s); // s 的值移动到函数里 ...// ... 所以到这里不再有效let x 5; // x 进入作用域makes_copy(x); // x 应该移动函数里// 但 i32 是 Copy 的所以在后面可继续使用 x} // 这里, x 先移出了作用域然后是 s。但因为 s 的值已被移走// 所以不会有特殊操作fn takes_ownership(some_string: String) { // some_string 进入作用域println!({}, some_string);
} // 这里some_string 移出作用域并调用drop方法占用的内存被释放fn makes_copy(some_integer: i32) { // some_integer 进入作用域println!({}, some_integer);
} // 这里some_integer移出作用域。不会有特殊操作对于返回值也同理。
fn main() {let s1 gives_ownership(); // gives_ownership 将返回值// 移给 s1let s2 String::from(hello); // s2 进入作用域let s3 takes_and_gives_back(s2); // s2 被移动到// takes_and_gives_back 中, // 它也将返回值移给 s3
} // 这里, s3 移出作用域并被丢弃。s2 也移出作用域但已被移走// 所以什么也不会发生。s1 移出作用域并被丢弃fn gives_ownership() - String { // gives_ownership 将返回值移动给// 调用它的函数let some_string String::from(hello); // some_string 进入作用域.some_string // 返回 some_string 并移出给调用的函数
}// takes_and_gives_back 将传入字符串并返回该值
fn takes_and_gives_back(a_string: String) - String { // a_string 进入作用域a_string // 返回 a_string 并移出给调用的函数
}引用与借用reference and borrow
如果我们想要函数使用一个值但不获取所有权该怎么办呢可以使用 引用 引用相反的操作是 解引用dereferencing它使用解引用运算符*。我们将获取引用作为函数参数称为借用borrowing。
fn main() {let s1 String::from(hello);let len calculate_length(s1);println!(The length of {} is {}., s1, len);
}fn calculate_length(s: String) - usize {s.len()
} // 这里s 离开了作用域。但因为它并不拥有引用值的所有权// 所以什么也不会发生s1 语法让我们创建一个指向值 s1 的引用但是并不拥有它。因为并不拥有这个值当引用离开作用域时其指向的值也不会被丢弃。 但是这个引用是不可变的如想要更改原引用的值可以使用mut
fn main() {let mut s String::from(hello);change(mut s);
}fn change(some_string: mut String) {some_string.push_str(, world);
}不过可变引用有一个很大的限制在特定作用域中的特定数据有且只有一个可变引用。下面的代码会报错。这个限制的好处是 Rust 可以在编译时就避免数据竞争data race。它可由这三个行为造成两个或更多指针同时访问同一数据至少有一个指针被用来写入数据没有同步数据访问的机制。数据竞争会导致未定义行为难以在运行时追踪并且难以诊断和修复Rust 避免了这种情况的发生因为它甚至不会编译存在数据竞争的代码
// 报错
let mut s String::from(hello);
let r1 mut s;
let r2 mut s;
println!({}, {}, r1, r2); // 不用r1就没事// 报错
let mut s String::from(hello);
let r1 s; // 没问题
let r2 s; // 没问题
let r3 mut s; // 大问题这里怎么想呢从语义来说r1和r2认为s是不变的结果变了
println!({}, {}, and {}, r1, r2, r3);// 上面这个如果变成下面这样是可以运行的也就是说只要你之后不再用r1和r2就没问题
let mut s String::from(hello);
let r1 s;
let r2 s;
let r3 mut s;
println!({}, r3);悬垂引用Dangling References
在具有指针的语言中很容易通过释放内存时保留指向它的指针而错误地生成一个悬垂指针dangling pointer所谓悬垂指针是其指向的内存可能已经被分配给其它持有者。相比之下在 Rust 中编译器确保引用永远也不会变成悬垂状态当你拥有一些数据的引用编译器确保数据不会在其引用之前离开作用域。
fn main() {let reference_to_nothing dangle();
}fn dangle() - String { // dangle 返回一个字符串的引用let s String::from(hello); // s 是一个新字符串s // 返回字符串 s 的引用
} // 这里 s 离开作用域并被丢弃。其内存被释放。// 解决方式直接返回s
fn no_dangle() - String {let s String::from(hello);s
}Slice 类型
用于提供对集合中一段连续元素的引用也就是能保证和原集合能够同步变化。“字符串 slice” 的类型声明写作 str
let s String::from(hello world);let hello s[0..5];
let world s[6..11];// 更好的写法可能是下面的因为它使得可以对 String 值和 str 值使用相同的函数
fn first_word(s: String) - str {
fn first_word(s: str) - str {结构体
// 声明
struct User {// fieldusername: String,email: String,sign_in_count: u64,active: bool,
}// 使用
let user1 User {email: String::from(someoneexample.com),username: String::from(someusername123),active: true,sign_in_count: 1,
};// 可变使用
// Rust并不允许只将某个字段标记为可变要想变整个实例都得可变
let mut user1 User {email: String::from(someoneexample.com),username: String::from(someusername123),active: true,sign_in_count: 1,
};
user1.email String::from(anotheremailexample.com);// 也可直接返回一个
fn build_user(email: String, username: String) - User {User {email: email,username: username,active: true,sign_in_count: 1,}
}// 使用没有命名字段的元组结构体来创建不同的类型
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
let black Color(0, 0, 0);
let origin Point(0, 0, 0);注意如果要在 Struct 里用字符串只能 String不能 str 。因为结构体拥有它的数据的所有权如果用 str 可以来自于外部 String 那么外部 String 失效会导致 struct 失效。
struct User {username: str,email: str,sign_in_count: u64,active: bool,
}fn main() {let user1 User {email: someoneexample.com,username: someusername123,active: true,sign_in_count: 1,};
}一个使用结构体的示例程序
// 计算长方形面积一步一步的重构
// 这两个参数是相关联的不过程序本身却没有表现出这一点。将长度和宽度组合在一起将更易懂也更易处理。
fn main() {let width1 30;let height1 50;println!(The area of the rectangle is {} square pixels.,area(width1, height1));
}fn area(width: u32, height: u32) - u32 {width * height
}// 使用元组重构不过元组并没有给出元素的名称所以计算变得更费解了因为不得不使用索引来获取元组的每一部分
fn main() {let rect1 (30, 50);println!(The area of the rectangle is {} square pixels.,area(rect1));
}fn area(dimensions: (u32, u32)) - u32 {dimensions.0 * dimensions.1
}// 使用结构体赋予更多意义
struct Rectangle {width: u32,height: u32,
}fn main() {let rect1 Rectangle { width: 30, height: 50 };println!(The area of the rectangle is {} square pixels.,area(rect1));
}fn area(rectangle: Rectangle) - u32 {rectangle.width * rectangle.height
}// 其他
// 通过派生 trait 增加实用功能
#[derive(Debug)] // 直接是无法打印struct的必须有Debug这个trait
struct Rectangle {width: u32,height: u32,
}fn main() {let rect1 Rectangle { width: 30, height: 50 };println!(rect1 is {:?}, rect1);// 另一种风格println!(rect1 is {:#?}, rect1);
}方法
fn 被称作函数而 impl 被称作方法。与 fn 不同的是impl 在结构体的上下文中被定义或者是枚举或 trait 对象的上下文通过 impl 块将方法与特定的结构体或枚举关联起来这意味着这些方法是与特定类型紧密相关的操作而不是一些独立的、无关的函数这有助于组织和模块化代码使得代码更加易于理解和维护。
并且 Rust 虽然不是一种传统的面向对象语言但它支持面向对象的一些核心概念比如封装、继承通过特质和多态。使用 impl 块定义方法是实现封装和多态的一种方式。它允许类型定义它们自己的行为以及通过特质为不同类型提供通用接口。
#[derive(Debug)]
struct Rectangle {width: u32,height: u32,
}// 这里的名字需要和struct一样
impl Rectangle {// self来替代rectangle: Rectangle如果想要改变rectangle需要变成mut self很少见fn area(self) - u32 {self.width * self.height}
}fn main() {let rect1 Rectangle { width: 30, height: 50 };println!(The area of the rectangle is {} square pixels., rect1.area());
}// 关联函数正如名字相关联的放在一个impl
// 它们仍是函数而不是方法因为它们并不作用于一个结构体的实例
// 使用时需要::let sq Rectangle::square(3);
impl Rectangle {fn square(size: u32) - Rectangle {Rectangle { width: size, height: size }}
}调用方法中 :: 和 . 的区别
就像上面提到的和结构体本身没什么联系的用 ::有联系的用 .。下面是一个其他例子这里 :: 其实就和 Java 中的 new 一个作用就是 new 出一个实例。
也有另一种情况比如在 main 中引入另一个包没有结构体包里面全是函数你要使用的话也得 ::。
struct Person {name: String,age: u8,
}impl Person {// 关联函数静态方法用于创建一个新的 Person 实例fn new(name: str, age: u8) - Person {Person {name: name.to_string(),age,}}// 实例方法调用时需要一个 Person 的实例fn say_hello(self) {println!(Hello, my name is {} and I am {} years old., self.name, self.age);}
}fn main() {// 调用关联函数静态方法来创建一个 Person 实例let person Person::new(Alice, 30);// 调用实例方法person.say_hello();
}枚举
什么时候使用枚举呢像下面的场景任何一个 IP 地址要么是 IPv4 的要么是 IPv6 的而且不能两者都是。IP 地址的这个特性使得枚举数据结构非常适合这个场景因为枚举值只可能是其中一个成员。
// 声明
enum IpAddrKind {V4,V6,
}// 使用
let four IpAddrKind::V4; // 这样写也暗示了V4和V6是属于IpAddrKind类型的
let six IpAddrKind::V6;// 对于函数
fn route(ip_type: IpAddrKind) { }
route(IpAddrKind::V4);枚举还可以存放数据
// 传统方式
enum IpAddrKind {V4,V6,
}
struct IpAddr {kind: IpAddrKind,address: String,
}
let home IpAddr {kind: IpAddrKind::V4,address: String::from(127.0.0.1),
};
let loopback IpAddr {kind: IpAddrKind::V6,address: String::from(::1),
};// 枚举方式
enum IpAddr {V4(String),V6(String),
}
let home IpAddr::V4(String::from(127.0.0.1));
let loopback IpAddr::V6(String::from(::1));// 更夸张的枚举可以存放任何混合类型
enum Message {Quit,Move { x: i32, y: i32 },Write(String),ChangeColor(i32, i32, i32),
}枚举也可以使用 impl。
impl Message {fn call(self) {// 在这里定义方法体}
}let m Message::Write(String::from(hello));
m.call();Option
Rust 并没有很多其他语言中有的空值功能与之类似的是 Option。OptionT 枚举是如此有用以至于它甚至被包含在了 prelude 之中你不需要将其显式引入作用域。另外它的成员也是如此可以不需要 Option:: 前缀来直接使用 Some 和 None。即便如此 OptionT 也仍是常规的枚举Some(T) 和 None 仍是 OptionT 的成员。
enum OptionT {Some(T),None,
}如果使用 None 而不是 Some需要告诉 Rust OptionT 是什么类型的因为编译器只通过 None 值无法推断出 Some 成员保存的值的类型。对于 T 是一个泛型任意类型的数据。
// 例子
let some_number Some(5);
let some_string Some(a string);
let absent_number: Optioni32 None;当有一个 Some 值时我们就知道存在一个值而这个值保存在 Some 中。当有个 None 值时在某种意义上它跟空值具有相同的意义并没有一个有效的值。那么重点来了OptionT 为什么就比空值要好呢
首先看下面的例子是会报错的因为Optioni8 与 i8 类型不同无法相加。当在 Rust 中拥有一个像 i8 这样类型的值时编译器确保它总是有一个有效的值我们可以自信使用而无需做空值检查而当使用 Optioni8 的时候需要担心可能没有值而编译器会确保我们在使用值之前处理了为空的情况。换句话说在对 OptionT 进行 T 的运算之前必须将其转换为 T。这意味着我们不得不处理可能为空的情况这样也就避免了遇到为空的问题。
// 报错
let x: i8 5;
let y: Optioni8 Some(5);
let sum x y;prelude
默认情况下Rust 会自动将 prelude 引入使得一些非常常用的类型和特质可以直接使用。如果需要的类型不在 prelude 中你必须使用use语句显式地将其引入作用域。另外常见的 std 为标准库prelude 属于 std。
match
// 常规用法
enum Coin {Penny,Nickel,Dime,Quarter(UsState),
}
fn value_in_cents(coin: Coin) - u8 {match coin {Coin::Penny 1,Coin::Nickel 5,Coin::Dime 10,Coin::Quarter(state) {println!(State quarter from {:?}!, state);25},}
}// 匹配Option
fn plus_one(x: Optioni32) - Optioni32 {match x {None None,Some(i) Some(i 1),}
}// 不像列举所有
match some_u8_value {1 println!(one),3 println!(three),5 println!(five),7 println!(seven),_ (),
}注意匹配必须是穷尽的exhaustive也体现出上面提到过Option的意义必须处理空值的情况
// 报错
fn plus_one(x: Optioni32) - Optioni32 {match x {Some(i) Some(i 1),}
}if let
let some_u8_value Some(0u8);
// 像这里我们其实只关心为Some(3)的情况我们可以使用if let
match some_u8_value {Some(3) println!(three),_ (),
}if let Some(3) some_u8_value {println!(three);
}然而这样会失去 match 强制要求的穷尽性检查。match 和 if let 之间的选择依赖特定的环境以及增加简洁度和失去穷尽性检查的权衡取舍。
// if let也可以加
let mut count 0;
if let Coin::Quarter(state) coin {println!(State quarter from {:?}!, state);
} else {count 1;
}这里区分一下之前出现的用法
if let 1 1 {println!(1);
}let a if true { // 这里必须放bool值1
} else {2
};项目管理
包package和 crate
包是提供一系列功能的一个或者多个 crate。一个包会包含有一个 Cargo.toml 文件阐述如何去构建这些 crate。
包中所包含的内容由几条规则来确立。一个包中至多只能包含一个库 cratelibrary crate包中可以包含任意多个二进制 cratebinary crate包中至少包含一个 crate无论是库的还是二进制的。
library crate是提供一组特定功能的代码库旨在被其它crate作为依赖项引用。它们不直接编译成可执行文件而是被编译成rlib或dylib文件供其它项目引用。通常用于定义函数、类型、trait等可以被其他项目重用的代码。库crate使得代码复用、模块化和分发变得简单。binary crate是编译成单独的可执行文件的crate它包含一个或多个main函数作为程序入口点。用于创建最终用户可以直接运行的应用程序。这些程序可以做任何事情从命令行工具到服务器和客户端应用程序。
当我们输入 Cargo newCargo 会给我们的包创建一个 Cargo.toml 文件。查看 Cargo.toml 的内容会发现并没有提到 src/main.rs因为 Cargo 遵循的一个约定src/main.rs 就是一个与包同名的二进制 crate 的 crate 根。同样的Cargo 知道如果包目录中包含 src/lib.rs则包带有与其同名的库 crate且 src/lib.rs 是 crate 根。crate 根文件将由 Cargo 传递给 rustc 来实际构建库或者二进制项目。如果一个包同时含有 src/main.rs 和 src/lib.rs则它有两个 crate一个库和一个二进制项且名字都与包相同。
额外说一下一个crate将将其功能保持在其自身的作用域中可以知晓一些特定的功能是在我们的 crate 中定义的还是在 别的crate 中定义的这可以防止潜在的冲突。
模块Module
模块让我们可以将一个 crate 中的代码进行分组以提高可读性与重用性。模块还可以控制项的私有性。
在 src/lib.rs下面的内容就是一个包含了其他内置了函数的模块的 front_of_house 模块。
mod front_of_house {mod hosting {fn add_to_waitlist() {}fn seat_at_table() {}}mod serving {fn take_order() {}fn server_order() {}fn take_payment() {}}
}在前面我们提到了src/main.rs 和 src/lib.rs 叫做 crate 根。之所以这样叫它们的原因是这两个文件的内容都是一个从名为 crate 的模块作为根的 crate 模块结构称为模块树module tree。
crate└── front_of_house├── hosting│ ├── add_to_waitlist│ └── seat_at_table└── serving├── take_order├── serve_order└── take_payment路径
路径用于引用模块树中的项分为绝对路径absolute path从 crate 根开始以 crate 名或者字面值 crate 开头相对路径relative path从当前模块开始以 self、super 或当前模块的标识符开头。路径的递进由 :: 完成。如何调用 add_to_waitlist 函数那就是用它的路径。
下面文件在 src/lib.rs 中 eat_at_restaurant 函数是我们 crate 库的一个公共API所以我们使用 pub 关键字来标记它。这个模块在模块树中与 eat_at_restaurant 定义在同一层级。
mod front_of_house {mod hosting {fn add_to_waitlist() {}}
}pub fn eat_at_restaurant() {// Absolute pathcrate::front_of_house::hosting::add_to_waitlist();// Relative pathfront_of_house::hosting::add_to_waitlist();
}什么时候用绝对路径什么时候相对路径呢如果我们要将 front_of_house 模块和 eat_at_restaurant 函数一起移动到一个名为新的 customer_experience 的模块中我们需要更新 add_to_waitlist 的绝对路径但是相对路径还是可用的。然而如果我们要将 eat_at_restaurant 函数单独移到一个名为新的 dining 的模块中还是可以使用原本的绝对路径来调用 add_to_waitlist但是相对路径必须要更新。我们更倾向于使用绝对路径因为它更适合移动代码定义和项调用的相互独立。
模块不仅对于你组织代码很有用。他们还定义了 Rust 的私有性边界privacy boundary这条界线不允许外部代码了解、调用和依赖被封装的实现细节。所以如果你希望创建一个私有函数或结构体你可以将其放入模块。
Rust 中默认所有项函数、方法、结构体、枚举、模块和常量都是私有的。父模块中的项不能使用子模块中的私有项但是子模块中的项可以使用他们父模块中的项。这是因为子模块封装并隐藏了他们的实现详情但是子模块可以看到他们定义的上下文。
像上面的代码其实是错误的如果想访问 hosting 以及 add_to_waitlist 需要为它添加 pub 字段。
mod front_of_house {pub mod hosting {pub fn add_to_waitlist() {}}
}pub fn eat_at_restaurant() {// Absolute pathcrate::front_of_house::hosting::add_to_waitlist();// Relative pathfront_of_house::hosting::add_to_waitlist();
}还可以使用 super 开头来构建从父模块开始的相对路径。这么做类似于文件系统中以 .. 开头的语法。下面的例子也可体现出子访问父不需要 pub。
fn serve_order() {}mod back_of_house {fn fix_incorrect_order() {cook_order();super::serve_order();}fn cook_order() {}
}还可以使用 pub 来设计公有的结构体和枚举不过有一些额外的细节需要注意。如果我们在一个结构体定义的前面使用了 pub 这个结构体会变成公有的但是这个结构体的字段仍然是私有的。我们可以根据情况决定每个字段是否公有。
mod back_of_house {pub struct Breakfast {pub toast: String,seasonal_fruit: String,}impl Breakfast {pub fn summer(toast: str) - Breakfast {Breakfast {toast: String::from(toast),// eat_at_restaurant无法更改所以必须在方法中做这步seasonal_fruit: String::from(peaches),}}}
}pub fn eat_at_restaurant() {let mut meal back_of_house::Breakfast::summer(Rye);meal.toast String::from(Wheat);println!(Id like {} toast please, meal.toast);
}与之相反如果我们将枚举设为公有则它的所有成员都将变为公有。给枚举的所有成员挨个添加 pub 是很令人恼火的因此枚举成员默认就是公有的。结构体通常使用时不必将它们的字段公有化因此结构体遵循常规内容全部是私有的除非使用 pub 关键字。
mod back_of_house {pub enum Appetizer {Soup,Salad,}
}pub fn eat_at_restaurant() {let order1 back_of_house::Appetizer::Soup;let order2 back_of_house::Appetizer::Salad;
}use
mod front_of_house {pub mod hosting {pub fn add_to_waitlist() {}}
}// 相对路径也是可以的
use crate::front_of_house::hosting;pub fn eat_at_restaurant() {hosting::add_to_waitlist();hosting::add_to_waitlist();hosting::add_to_waitlist();
}为什么不用 use crate::front_of_house::hosting::add_to_waitlist; 呢要想使用 use 将函数的父模块引入作用域我们必须在调用函数时指定父模块这样可以清晰地表明函数不是在本地定义的而且可以避免和本地函数命名重复
另一方面使用 use 引入结构体、枚举和其他项时习惯是指定它们的完整路径直接用。没什么原因就是习惯用法。但是如果名称相同还是要引入父模块。
use std::collections::HashMap;fn main() {let mut map HashMap::new();map.insert(1, 2);
}use std::fmt;
use std::io;fn function1() - fmt::Result {
}fn function2() - io::Result() {
}也可使用 as 关键字提供新的名称
use std::fmt::Result;
use std::io::Result as IoResult;fn function1() - Result {
}fn function2() - IoResult() {
}如果想让外部调用你的代码的代码去调用 crate::front_of_house::hosting 可以加个 pub。
pub use crate::front_of_house::hosting;使用外部包
// 需要安装
use rand::Rng;嵌套路径
use std::cmp::Ordering;
use std::io;
// 化简
use std::{cmp::Ordering, io};use std::io;
use std::io::Write;
// 化简
use std::io::{self, Write};想要引入所有
use std::collections::*;以上所有的例子都在一个文件中定义多个模块。如何将模块分出去呢
对于src/front_of_house.rs在src/lib.rs使用 mod 实现
mod front_of_house;pub use crate::front_of_house::hosting;pub fn eat_at_restaurant() {hosting::add_to_waitlist();hosting::add_to_waitlist();hosting::add_to_waitlist();
}一个使用相对路径的例子
src/front_of_house.rs
pub mod hosting;src/front_of_house/hosting.rs
pub fn add_to_waitlist() {}集合
Vector
// 声明需要类型
let v: Veci32 Vec::new();// 直接利用vec宏来创建
let v vec![1, 2, 3];// 更新需要用mut声明
let mut v Vec::new();
v.push(5);对于获取其元素有两种方式。对于第一种方式当使用一个不存在的元素时 Rust 会造成 panic这个方法更适合当程序认为尝试访问超过 vector 结尾的元素是一个严重错误的情况这时应该使程序崩溃。对于第二个方式当 get 方法被传递了一个数组外的索引时它不会 panic 而是返回 None。当偶尔出现超过 vector 范围的访问属于正常情况的时候可以考虑使用它接着代码可以有处理 Some 或 None 的逻辑。
let v vec![1, 2, 3, 4, 5];let does_not_exist v[100];match v.get(2) {Some(third) println!(The third element is {}, third),None println!(There is no third element.),
}可能会遇到的错误。
// 同时存在可变和不可变
let mut v vec![1, 2, 3, 4, 5];
let first v[0];
v.push(6);
println!(The first element is: {}, first);为什么要用引用访问下面的代码会报错可以发现不能仅仅把一个值的所有权转移要转一起转。而对于整数这种i32 实现了 Copy trait所以取出来的是复制的值所以不会有影响。所以还是加引用吧因为你不需要拿到所有权。
fn main() {let a String::from(hello);let v vec![a];{// 报错let a v[0];// 可以这么写不会报错// let a v;println!({}, a);}println!({}, v[0]);
}// 不会报错
fn main() {let v vec![1, 2, 3];{let a v[0];println!({}, a);}println!({}, v[0]);
}遍历
let v vec![100, 32, 57];
for i in v {println!({}, i);
}// 更改值
let mut v vec![100, 32, 57];
for i in mut v {// 引用的原值是无法改变的需要解引用*i 50;
}如何在 Vector 中存储多种不同类型的值可以利用 enum
enum SpreadsheetCell {Int(i32),Float(f64),Text(String),
}let row vec![SpreadsheetCell::Int(3),SpreadsheetCell::Text(String::from(blue)),SpreadsheetCell::Float(10.12),
];字符串
新建字符串
// 第一种方式
let mut s String::new();
let data initial contents;
let s data.to_string();
// 或者
let s initial contents.to_string();// 第二种方式
let s String::from(initial contents);更新字符串
let mut s String::from(foo);
s.push_str(bar);// s2还是能用的因为push_str方法采用字符串slice因为我们并不需要获取参数的所有权。
let mut s1 String::from(foo);
let s2 bar;
s1.push_str(s2);
println!(s2 is {}, s2);// 单独字符
let mut s String::from(lo);
s.push(l);// 拼接
let s1 String::from(Hello, );
let s2 String::from(world!);
// 必须这么用第一个不加引用后面的加
let s3 s1 s2; // 注意 s1 被移动了不能继续使用这里会出现个问题下面的代码为什么 s 不会随着 s2 改变。这是因为 实际调用了 fn add(self, s: str) - String { 函数里面会将s的值复制而不是用它的引用。这里还有个问题为什么 String能传进 add 中是因为 String 可以被 强转coerced成 strRust 使用了一个被称为解引用强制多态deref coercion的技术你可以将其理解为它把 s2 变成了 s2[..]。
let s1 String::from(tic);
let mut s2 String::from(toe);
let s s1 s2;
s2 678.to_string();
println!({}, s);索引字符串
// String不支持字符串索引
let s1 String::from(hello);
let h s1[0];为啥不行Rust 提供了多种不同的方式来解释计算机储存的原始字符串数据这样程序就可以选择它需要的表现方式而无所谓是何种人类语言包括字节、标量值不一定是字符有的语音比较特别会加语调、字形簇类似字符。而且索引操作预期总是需要常数时间 (O(1))。但是对于 String 不可能保证这样的性能因为 Rust 必须从开头到索引位置遍历来确定有多少有效的字符。
字符串 slice
不好用
let hello Здравствуйте;
let s hello[0..4];
// 实际输出为Зд
// 如果hello[0..1]会报错遍历
for c in नमस्ते.chars() {println!({}, c);
}
//न म स ् त ेfor b in नमस्ते.bytes() {println!({}, b);
}
// 224 164 165 135// 从字符串中获取字形簇是很复杂的所以标准库并没有提供这个功能。crates.io 上有些提供这样功能的 crate哈希map
新建
use std::collections::HashMap;let mut scores HashMap::new();
scores.insert(String::from(Blue), 10);
scores.insert(String::from(Yellow), 50);// 另一个不常用的方法
let teams vec![String::from(Blue), String::from(Yellow)];
let initial_scores vec![10, 50];
let scores: HashMap_, _ teams.iter().zip(initial_scores.iter()).collect();所有权
let field_name String::from(Favorite color);
let field_value String::from(Blue);let mut map HashMap::new();
// 插入后会剥夺所有权
map.insert(field_name, field_value);访问
let mut scores HashMap::new();scores.insert(String::from(Blue), 10);
scores.insert(String::from(Yellow), 50);let team_name String::from(Blue);
let score scores.get(team_name);
// 直接遍历
for (key, value) in scores {println!({}: {}, key, value);
}修改值
// 直接覆盖
scores.insert(String::from(Blue), 10);
scores.insert(String::from(Blue), 25);// 只在键没有对应值时插入
scores.insert(String::from(Blue), 10);
scores.entry(String::from(Blue)).or_insert(50);//
let text hello world wonderful world;
let mut map HashMap::new();
for word in text.split_whitespace() {// or_insert会返回一个可变引用// 你如果想用or_insert就得用entry它用于查找是否存在一个键let count map.entry(word).or_insert(0);// 引用不能加减要解引用*count 1;
}
文章转载自: http://www.morning.yrjkp.cn.gov.cn.yrjkp.cn http://www.morning.rjxwq.cn.gov.cn.rjxwq.cn http://www.morning.ntwfr.cn.gov.cn.ntwfr.cn http://www.morning.pthmn.cn.gov.cn.pthmn.cn http://www.morning.gxeqedd.cn.gov.cn.gxeqedd.cn http://www.morning.lysrt.cn.gov.cn.lysrt.cn http://www.morning.qyfrd.cn.gov.cn.qyfrd.cn http://www.morning.gnmhy.cn.gov.cn.gnmhy.cn http://www.morning.tjkth.cn.gov.cn.tjkth.cn http://www.morning.zljqb.cn.gov.cn.zljqb.cn http://www.morning.mwrxz.cn.gov.cn.mwrxz.cn http://www.morning.dpdr.cn.gov.cn.dpdr.cn http://www.morning.mxdiy.com.gov.cn.mxdiy.com http://www.morning.lgznf.cn.gov.cn.lgznf.cn http://www.morning.hlshn.cn.gov.cn.hlshn.cn http://www.morning.btrfm.cn.gov.cn.btrfm.cn http://www.morning.dwyyf.cn.gov.cn.dwyyf.cn http://www.morning.wqfzx.cn.gov.cn.wqfzx.cn http://www.morning.fmqw.cn.gov.cn.fmqw.cn http://www.morning.frpm.cn.gov.cn.frpm.cn http://www.morning.dpsgq.cn.gov.cn.dpsgq.cn http://www.morning.tpfny.cn.gov.cn.tpfny.cn http://www.morning.shnqh.cn.gov.cn.shnqh.cn http://www.morning.spnky.cn.gov.cn.spnky.cn http://www.morning.lltdf.cn.gov.cn.lltdf.cn http://www.morning.fnxzk.cn.gov.cn.fnxzk.cn http://www.morning.ybshj.cn.gov.cn.ybshj.cn http://www.morning.lbzgt.cn.gov.cn.lbzgt.cn http://www.morning.skbbt.cn.gov.cn.skbbt.cn http://www.morning.xnqjs.cn.gov.cn.xnqjs.cn http://www.morning.nyqb.cn.gov.cn.nyqb.cn http://www.morning.tkcz.cn.gov.cn.tkcz.cn http://www.morning.skfkx.cn.gov.cn.skfkx.cn http://www.morning.fdzzh.cn.gov.cn.fdzzh.cn http://www.morning.lnfkd.cn.gov.cn.lnfkd.cn http://www.morning.bytgy.com.gov.cn.bytgy.com http://www.morning.ksggr.cn.gov.cn.ksggr.cn http://www.morning.bpmfz.cn.gov.cn.bpmfz.cn http://www.morning.qpqcq.cn.gov.cn.qpqcq.cn http://www.morning.ldwxj.cn.gov.cn.ldwxj.cn http://www.morning.bsplf.cn.gov.cn.bsplf.cn http://www.morning.yjdql.cn.gov.cn.yjdql.cn http://www.morning.jpnw.cn.gov.cn.jpnw.cn http://www.morning.lgsqy.cn.gov.cn.lgsqy.cn http://www.morning.khzml.cn.gov.cn.khzml.cn http://www.morning.ptmgq.cn.gov.cn.ptmgq.cn http://www.morning.rqnhf.cn.gov.cn.rqnhf.cn http://www.morning.gjws.cn.gov.cn.gjws.cn http://www.morning.gghhmi.cn.gov.cn.gghhmi.cn http://www.morning.pyzt.cn.gov.cn.pyzt.cn http://www.morning.npkrm.cn.gov.cn.npkrm.cn http://www.morning.mkrjf.cn.gov.cn.mkrjf.cn http://www.morning.crdtx.cn.gov.cn.crdtx.cn http://www.morning.djbhz.cn.gov.cn.djbhz.cn http://www.morning.qhczg.cn.gov.cn.qhczg.cn http://www.morning.fdhwh.cn.gov.cn.fdhwh.cn http://www.morning.cjnfb.cn.gov.cn.cjnfb.cn http://www.morning.hsxkq.cn.gov.cn.hsxkq.cn http://www.morning.rwfj.cn.gov.cn.rwfj.cn http://www.morning.qqtzn.cn.gov.cn.qqtzn.cn http://www.morning.skmzm.cn.gov.cn.skmzm.cn http://www.morning.tfwr.cn.gov.cn.tfwr.cn http://www.morning.wmdqc.com.gov.cn.wmdqc.com http://www.morning.bgnkl.cn.gov.cn.bgnkl.cn http://www.morning.irqlul.cn.gov.cn.irqlul.cn http://www.morning.rhkmn.cn.gov.cn.rhkmn.cn http://www.morning.zsthg.cn.gov.cn.zsthg.cn http://www.morning.hnrls.cn.gov.cn.hnrls.cn http://www.morning.bpmtx.cn.gov.cn.bpmtx.cn http://www.morning.zycll.cn.gov.cn.zycll.cn http://www.morning.rlns.cn.gov.cn.rlns.cn http://www.morning.rfwkn.cn.gov.cn.rfwkn.cn http://www.morning.ggnfy.cn.gov.cn.ggnfy.cn http://www.morning.gjcdr.cn.gov.cn.gjcdr.cn http://www.morning.lsnnq.cn.gov.cn.lsnnq.cn http://www.morning.dmrjx.cn.gov.cn.dmrjx.cn http://www.morning.gbcnz.cn.gov.cn.gbcnz.cn http://www.morning.rgwz.cn.gov.cn.rgwz.cn http://www.morning.rklgm.cn.gov.cn.rklgm.cn http://www.morning.qqhfc.cn.gov.cn.qqhfc.cn