# 创建项目
cargo new <project_name>
# 创建库
cargo new --lib <library_name>
# 构建项目
cargo build
# 构建并运行项目
cargo run
# 检查代码
cargo check
# 发布构建
cargo build --release
2、变量声明
let a = 5; // 不可变变量
a = 6; // Error
let mut b = 5; // 可变变量
b = 6; // OK
let spaces = " ";
let spaces = spaces.len(); // Shadowing,隐藏先前的变量
println!("{}", spaces ); // 5
fn main() {
// 加法
let add = 5 + 10;
// 减法
let subtract = 95.5 - 4.3;
// 乘法
let multiple = 4 * 30;
// 除法
let divide = 56.7 / 32.2;
let divide_floored = 2 / 3; // 整数除法会向下取整
// 取余
let remain = 43 % 5;
}
(4)布尔型
fn main() {
let t = true;
let f: bool = false;
}
(5)字符型
fn main() {
let char = 'a';
let emoji = '😻'; // 4 bytes Unicode
let str = "Hello World";
}
(6)元组类型
fn main() {
let tup = (500, 6.4, "hello");
let tup: (i32, f64, &str) = (500, 6.4, "hello");
// 解构
let (x, y, z) = tup;
let (first, _, _) = tup;
println!("x: {}, y: {}, z: {}", x, y, z);
println!("x: {}, y: {}, z: {}", tup.0, tup.1, tup.2);
println!("first: {}", first);
}
(7)数组类型
数组中的每个元素的类型必须相同,数组长度是固定的。
如需不固定长度,使用标准库提供的Vector 类型,一个允许增长和缩小长度的类似数组的集合类型。
fn main() {
let a = [1, 2, 3, 4, 5];
let a: [i32; 5] = [1, 2, 3, 4, 5];
let first = a[0];
let second = a[1];
}
4、函数
几个概念:
字面量:5(整形字面量)、”hello”(字符串字面量)……
表达式:105 + 6、x + 1 ……(会产生一个值)
语句:let i = 5;x + 1; (以;结束,没有返回值,不能作为表达式使用)……
函数由一系列语句组成,最后由一个可选的表达式结束。
fn five () -> i32 {
5
}
fn add(x: i32, y: i32) -> i32 {
return x + y;
}
fn foo() -> i32 {
let x = 5;
x + 1; // 分号结束是语句,不能作为返回值
x + 1 // 没有分号,作为表达式
}
fn bar() -> i32 {
// 代码块也是表达式
{
let i = 5;
i + 2 // 表达式,作为代码块的返回值
}
}
5、条件
fn main() {
let number = 3;
if number < 5 {
println!("condition was true");
} else {
println!("condition was false");
}
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");
}
}
fn main() {
let n = 5;
let m = 10;
let max = if n > m { n } else { m };
println!("{}", max);
}
栈和堆都是代码在运行时可供使用的内存,但是它们的结构不同。栈以放入值的顺序存储值并以相反顺序取出值。这也被称作 后进先出(last in, first out)。想象一下一叠盘子:当增加更多盘子时,把它们放在盘子堆的顶部,当需要盘子时,也从顶部拿走。不能从中间也不能从底部增加或拿走盘子!增加数据叫做 进栈(pushing onto the stack),而移出数据叫做 出栈(popping off the stack)。栈中的所有数据都必须占用已知且固定的大小。在编译时大小未知或大小可能变化的数据,要改为存储在堆上。 堆是缺乏组织的:当向堆放入数据时,你要请求一定大小的空间。内存分配器(memory allocator)在堆的某处找到一块足够大的空位,把它标记为已使用,并返回一个表示该位置地址的 指针(pointer)。这个过程称作 在堆上分配内存(allocating on the heap),有时简称为 “分配”(allocating)。将数据推入栈中并不被认为是分配。因为指针的大小是已知并且固定的,你可以将指针存储在栈上,不过当需要实际数据时,必须访问指针。想象一下去餐馆就座吃饭。当进入时,你说明有几个人,餐馆员工会找到一个够大的空桌子并领你们过去。如果有人来迟了,他们也可以通过询问来找到你们坐在哪。
访问堆上的数据比访问栈上的数据慢,因为必须通过指针来访问。现代处理器在内存中跳转越少就越快(缓存)。继续类比,假设有一个服务员在餐厅里处理多个桌子的点菜。在一个桌子报完所有菜后再移动到下一个桌子是最有效率的。从桌子 A 听一个菜,接着桌子 B 听一个菜,然后再桌子 A,然后再桌子 B 这样的流程会更加缓慢。出于同样原因,处理器在处理的数据彼此较近的时候(比如在栈上)比较远的时候(比如可能在堆上)能更好的工作。在堆上分配大量的空间也可能消耗时间。
fn main(){
let mut s = String::from("hello");
let r1 = &mut s; // OK
let r2 = &mut s; // ERROR,在同一时间只能有一个对某一特定数据的可变引用
println!("{}, {}", r1, r2);
}
fn main (){
let mut s = String::from("hello");
{
let r1 = &mut s;
} // r1 在这里离开了作用域,所以我们完全可以创建一个新的引用
let r2 = &mut s;
}
可变引用与不可变引用作用域不可重叠:
fn main() {
let mut s = String::from("hello");
let r1 = &s; // OK
let r2 = &s; // OK
let r3 = &mut s; // ERROR,不可同时拥有可变引用和不可变引用
println!("{}, {}, and {}", r1, r2, r3);
}
fn main(){
let mut s = String::from("hello");
let r1 = &s; // OK
let r2 = &s; // OK
println!("{} and {}", r1, r2);
// 此位置之后 r1 和 r2 不再使用
let r3 = &mut s; // OK
println!("{}", r3);
}
10、Slice
fn main(){
let s = String::from("hello world");
let len = s.len();
let hello = &s[0..5];
let world = &s[6..11];
// 等价
let slice = &s[0..2];
let slice = &s[..2];
// 等价
let slice = &s[3..len];
let slice = &s[3..];
// 等价
let slice = &s[0..len];
let slice = &s[..];
}
fn main() {
let mut s = String::from("hello world");
let word = first_word(&s); // word 为 s 的一个不可变引用
s.clear(); // ERROR,clear 尝试获取可变引用,同时存在不可变引用
println!("the first word is: {}", word); // word 到此依然有效
}
字符串字面值就是 slice,即&str,一个不可变引用。
fn first_word(s: &str) -> &str {
// ...
}
fn main() {
let my_string_literal = "hello world";
let word = first_word(&my_string_literal[0..6]); // OK
let word = first_word(&my_string_literal[..]); // OK
let word = first_word(my_string_literal); // OK
let my_string = String::from("hello world");
let word = first_word(&my_string[0..6]); // OK
let word = first_word(&my_string[..]); // OK
let word = first_word(&my_string); // OK
}
enum Option<T> {
None,
Some(T), // 绑定泛型值
}
fn main() {
let nan: Option<i8> = Option::None;
let num: Option<i8> = Option::Some(1);
match num {
Option::None => {
println!("num is not a number");
}
Option::Some(n) => { // 访问绑定值
println!("nam value is {}", n);
}
}
}
(3)通用匹配模式
fn main() {
let roll = 9;
match roll {
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
other => move_player(other), // other 通用匹配
// _ => reroll(), // 忽略绑定值
// _ => (), // 空处理
}
}
fn add_fancy_hat() {}
fn remove_fancy_hat() {}
fn move_player(num_spaces: u8) {}
14、if let控制流
fn main() {
// ===== 使用 match =====
let num = Some(5);
match num {
Some(n) => println!("{}", n),
_ => (),
}
// ===== 使用 if let 简写=====
let num = Some(5);
if let Some(n) = num {
println!("{}", n);
} // 还可以支持 else 处理其他情况
}
1、Cargo常用命令
2、变量声明
3、基本标量数据类型
(1)整形
(2)浮点型
浮点数采用 IEEE-754 标准表示。
(3)数值运算
(4)布尔型
(5)字符型
(6)元组类型
(7)数组类型
数组中的每个元素的类型必须相同,数组长度是固定的。
4、函数
几个概念:
5
(整形字面量)、”hello”
(字符串字面量)……10
5 + 6
、x + 1
……(会产生一个值)let i = 5;
x + 1;
(以;
结束,没有返回值,不能作为表达式使用)……函数由一系列
语句
组成,最后由一个可选的表达式
结束。5、条件
6、循环
(1)loop
(2)while
(3)for
7、栈(Stack)与堆(Heap)
在很多语言中,你并不需要经常考虑到栈与堆。不过在像 Rust 这样的系统编程语言中,值是位于栈上还是堆上在更大程度上影响了语言的行为以及为何必须做出这样的抉择。我们会在本章的稍后部分描述所有权与栈和堆相关的内容,所以这里只是一个用来预热的简要解释。
栈和堆都是代码在运行时可供使用的内存,但是它们的结构不同。栈以放入值的顺序存储值并以相反顺序取出值。这也被称作 后进先出(last in, first out)。想象一下一叠盘子:当增加更多盘子时,把它们放在盘子堆的顶部,当需要盘子时,也从顶部拿走。不能从中间也不能从底部增加或拿走盘子!增加数据叫做 进栈(pushing onto the stack),而移出数据叫做 出栈(popping off the stack)。栈中的所有数据都必须占用已知且固定的大小。在编译时大小未知或大小可能变化的数据,要改为存储在堆上。 堆是缺乏组织的:当向堆放入数据时,你要请求一定大小的空间。内存分配器(memory allocator)在堆的某处找到一块足够大的空位,把它标记为已使用,并返回一个表示该位置地址的 指针(pointer)。这个过程称作 在堆上分配内存(allocating on the heap),有时简称为 “分配”(allocating)。将数据推入栈中并不被认为是分配。因为指针的大小是已知并且固定的,你可以将指针存储在栈上,不过当需要实际数据时,必须访问指针。想象一下去餐馆就座吃饭。当进入时,你说明有几个人,餐馆员工会找到一个够大的空桌子并领你们过去。如果有人来迟了,他们也可以通过询问来找到你们坐在哪。
入栈比在堆上分配内存要快,因为(入栈时)分配器无需为存储新数据去搜索内存空间;其位置总是在栈顶。相比之下,在堆上分配内存则需要更多的工作,这是因为分配器必须首先找到一块足够存放数据的内存空间,并接着做一些记录为下一次分配做准备。
访问堆上的数据比访问栈上的数据慢,因为必须通过指针来访问。现代处理器在内存中跳转越少就越快(缓存)。继续类比,假设有一个服务员在餐厅里处理多个桌子的点菜。在一个桌子报完所有菜后再移动到下一个桌子是最有效率的。从桌子 A 听一个菜,接着桌子 B 听一个菜,然后再桌子 A,然后再桌子 B 这样的流程会更加缓慢。出于同样原因,处理器在处理的数据彼此较近的时候(比如在栈上)比较远的时候(比如可能在堆上)能更好的工作。在堆上分配大量的空间也可能消耗时间。
当你的代码调用一个函数时,传递给函数的值(包括可能指向堆上数据的指针)和函数的局部变量被压入栈中。当函数结束时,这些值被移出栈。
跟踪哪部分代码正在使用堆上的哪些数据,最大限度的减少堆上的重复数据的数量,以及清理堆上不再使用的数据确保不会耗尽空间,这些问题正是所有权系统要处理的。一旦理解了所有权,你就不需要经常考虑栈和堆了,不过明白了所有权的主要目的就是为了管理堆数据,能够帮助解释为什么所有权要以这种方式工作。
8、所有权
所有权是针对堆数据产生的,规则如下:
9、引用与借用
对同一数据的多个可变引用,作用域不可重叠:
可变引用与不可变引用作用域不可重叠:
10、Slice
字符串字面值就是 slice,即&str,一个不可变引用。
11、结构体
(1)基本用法
(2)元组结构体
(3)类单元结构体
(4)结构体方法(关联函数)
(5)结构体非关联函数
12、枚举
(1)基本用法
(2)绑定值
(3)复杂的绑定值、定义枚举方法
13、Match控制流运算符
(1)基本用法
(2)绑定值
(3)通用匹配模式
14、if let控制流
待续。。。