Rust 101
Rust 入门,笔记,集合:vol 1
我学 《The Rust Programming Language》by Steve Klabnik and Carol Nichols, with contributions from the Rust Community。
伟大的开源中文译本:https://rustwiki.org/zh-CN/book/
Hello World
// hello.rs
fn main() {
println!("Hello, world!");
}
$ rustc hello.rs
$ ./hello
Hello, world!
cargo
:包管理、项目组织构建
$ cargo new hello_cargo # 新建项目
$ cd hello_cargo
$ vim src/main.rs
$ cargo run # 运行代码
$ cargo build # 编译:debug
$ cargo build --release # 编译:release
Rust 量
- 常量:
const
始终不可变 - 变量:
let
:默认不可变let mut
:只可变,类型不可变
遮蔽(shadow):作用域内重新声明同名变量。
- 名不变
- 值/类型可变
{
let foo = 1; # i64: 1
let foo = 2.0; # f64: 2.0
{
let foo = "3"; # &str: "3"
}
# f64: 2.0
}
Rust 静态类型
静态类型:编译时确定所有变量的类型。
Rust 标量
类型 | 类型 | 说明 |
---|---|---|
整型 | i/u + size :i16 ,u32 ,… |
* isize : 由机器架构决定(64 位机:isize=i64 )* usize : 用作索引值 |
浮点型 | f32 ,f64 |
默认 f64 ,IEEE 754 格式 |
布尔型 | bool :`true |
false` |
字符 | char |
4 byte:unicode |
算术
+
,-
,*
,/
,%
,…
整数
/
:向下取整
整型:字面量
进制 | 字面量 |
---|---|
二 | 0b1011 |
八 | 0o76 |
十 | 114_514 |
十六 | 0xfe |
字节(u8) | b'A' |
整型:溢出
- debug:panic
- release:二进制补(正常溢出)or 其他可选行为
Rust 复合类型
元组 ()
let tup: (i32, f64, u8) = (500, 6.4, 1);`
- 多种类型 多个值 放一起,长度固定
- 解构:
let (x, y, z) = tup;
- 索引:
let x = tup.0;
- 单元类型:
()
,单元值:()
- 表达式不返回 =>
return ();
- 表达式不返回 =>
数组 []
let a: [ i32 ; 5 ] = [1, 2, 3, 4, 5];
类型 长度
- 相同类型,长度固定
- Repeat:
let a = [3; 5];
=> 5 个 3 - 访问:
let first = a[0];
- 越界:runtime panic
Tips:用 dbg!
宏答应表达式的值到 stderr
let foo = dbg!(表达式);
└──> 打印并返回值
Rust 函数
fn 名(参, 数) 〔-> 返回类型〕 {
语句
...
〔返回值表达式〕 // 可选,无则 return ()
}
注:六角括号 〔〕
表示内容可选
语句 vs 表达式
- 语句:操作,不返回值
- 表达式:计算,产生返回值
- 函数、宏调用,代码块
{ ... }
都是表达式
- 函数、宏调用,代码块
语句 = 表达式 +
;
let y = {
let x = 3;
x + 1 // 整个代码块值为 4 所以 y = 4
}
Rust 控制流
if 条件
if 表达式 {
// 表达式必须返回 bool 值,不会隐式转换
...
} else if ... {
...
} else {
...
}
if
是表达式,有返回结果:
let num = if cond {
5 } else {
6 };
循环
loop
:无限循环(可以 break 出来)while
:while 条件 { ... }
for
:for element in array { ... }
break
:退出并从循环返回值
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
}
Range:
for n in (1..4).rev() {
// .rev() 把 Range 反转
...
}
Rust 所有权
管理权(ownership)主要是管理堆数据
Rust 值的存放:
- stack:编译时已知固定大小的数据
- heap:编译时大小未知或不定的数据
所有权规则:
- 每个值在任一时刻都有且只有一个所有者(owner)变量
- owner 离开作用域,值被丢弃(调用 drop 函数进行销毁)
移动 vs 克隆
栈值克隆:
let x = 5;
let y = 5;
// 栈中压入两个独立的 5 值
// 自动复制:一定是深拷贝
堆值移动:
let s1 = String::from("hello");
let s2 = s1; // 值从 s1 移动到了 s2
// s1 不再可用:避免 double free
// Rust 没有浅拷贝:只移动,不拷贝
自动复制一定是运行时对性能影响小的深拷贝。
要克隆堆值,必须使用显式的深拷贝:
let s1 = String::from("hi");
let s2 = s1.clone();
trait: Copy & Drop
实际上,起决定作用的并不是“栈值”、“堆值”,而是看类型实现了那种 trait(二选一):
-
Copy
:可克隆let new = old; // 之后 old 仍可用
-
Drop
:要移动离开作用域时,值被 drop
标量(整浮布字)都 impl 了 Copy;
元组所有元素 Copy,则元组 Copy;
所有权 & 函数
值传给函数:同赋值:
- Copy 的复制
- Drop 的移动:值移动到了 fn 里:外面(调用后面)不能用了
let i = 42;
let s = String::from("hi");
function(i, s);
println!("{}", i); // ✅
println!("{}", s); // ❌
引用、借用
引用:类似于 C 的指针
- 引用:
&
- 解引用:
*
let s = String::from("hi");
let l = len(&s);
fn len(s: &String) -> usize {
s.len() // == (*s).len()
}
借用:传 &s
不让函数拥有 s 的所有权
- 函数 end,离开作用域 => 不 drop 值
可变引用
&s
只读引用&mut s
可用于修改 s 的值
let mut s = String::from("hello ");
change(&mut s); // 形实都要加 mut
fn change(s: &mut String) {
s.push_str("world");
}
同一时间,一个数据只能有多个只读引用或一个可变引用。
变 + 变 | 不变 + 变 | 不变 + 不变 |
---|---|---|
❌ | ❌ | ✅ |
=> (在编译时)避免(并发时) data race
let mut s = String::from("hi");
{
let r0 = &mut s; } // r0 离域,不再存在
let r1 = &mut s; // ✅ r1 一个 &mut
let r2 = &mut s; // ❌ r1、r2 两个 &mut
非词法作用域生命周期(Non-Lexical Lifetime,NLL)
引用的作用域是「声明 -> 最后一次使用」。
∴ 可以:
let ir = &s;
println!("{}", &ir); // ir 不再使用:声明周期结束
let mr = &mut s; // ✅
println!("{}", &mr);
悬垂引用
悬垂(dangling)引用:指向已释放的内存的引用。
=> Rust 直接编译时 Error
fn d() -> &String {
let s = &String::from("hi");
&s // ❌ s 离开作用域即被 drop,&s 悬垂
}
slice
切片(slice):引用集合中的一段连续的元素。
let s = String::from("hello world");
let hello= &s[0..5]; // [0, 5)
// hello: &str => String 的 slice 是 &str
s.clear() // ❌ 这个方法借 &mut s,
// 但 hello 是借了个 immut
// immut + mut -> error
切片是一个不可变引用,结合“不能变+不变”的引用规则
=> 保证 slice 始终可用、未变。
字符串常量:
- 类型:
&str
- 指向二进制程序中的某位置
一般处理字符串的函数都用
fn f(s: &str)
这样传参
String
对象和&str
(包括字面量)都可。
Rust struct
一般结构体
定义:
struct User {
email: String
age: u8
}
实例化:
let mut foo = User {
email: String::from("[email protected]"),
age: 8,
};
println!("{}", foo.email);
foo.email = String::from("[email protected]");
语法糖:变量名与字段同名 => 简写
{foo: foo, } => {foo, }
fn build_user(email: String, age: u8) -> User {
User {
email,
age,
}
}
结构体更新语法
let user2 = User {
email: String::from("[email protected]"),
..user1, // 除了显式写的 email 字段,其余用 user1 的值
}
- 若
user1
中的 Drop 值被user2
使用,则等于移动到了user2
,user1
不再可用; - 若
..user1
只用了user1
中的 Copy 值,则之后user1
仍然可用:- User 中全是 Copy
- 或 Drop 值全被显式给出
元组结构体
元组结构体:无字段名,匿名结构体。
struct Color(