导图社区 Rust语言基础
Rust语言富有前景,本文档是学习Rust后的精简解读。Rust是一门系统编程语言 ,专注于安全 ,尤其是并发安全,支持函数式和命令式以及泛型等编程范式的多范式语言。Rust在语法上和C++类似 ,但是设计者想要在保证性能的同时提供更好的内存安全。
编辑于2021-04-20 19:06:23Rust
语法
语句以分号结束,复合语句的最后一个语句不使用分号
注释
单行注释
// 注释内容
段落注释
/* 注释内容 */
声明常量
const 常量名 = 值;
const 常量名:数据类型 = 值;
如果编译器可以推断出常量的数据类型,则类型可以省略
const MAX_DATA:u32=1000;
const MAX_U16=65_536;
const PI=3.14;
const UNIT="元";
声明变量
const 常量名 = 值;
const 常量名:数据类型 = 值;
如果编译器可以推断出变量的数据类型,则类型可以省略
let _i32=100;
let _u32:u32=1000;
let s="字符串切片";
let s:String="字符串".to_string();
声明可变变量
let mut var=123;
let mut s="字符串切片";
let mut s:String="字符串".to_string();
如果没有明确使用mut关键词,则变量在第一次赋值后,不能再重新赋值
条件语句
if 条件表达式 { println!("条件为 true"); } else { println!("条件为 false"); }
注意点
条件表达式语句不用圆括号
条件表达式必须是bool类型
和c/c++的区别
可以只有if,不是else
可以连续使用 else if
循环语句
loop
let result = loop { 语句 ... break; }
注意点
如果没有显式的break语句中断循环,则会一直循环,直到出现异常
loop 循环可以通过 break 关键字类似于 return 一样使整个循环退出并给予外部一个返回值,但返回值不是必须的
while
while 条件表达式 { 语句 ... break; }
注意点
如果没有显式的break语句中断循环,则会一直循环,直到出现异常
loop 循环可以通过 break 关键字类似于 return 一样使整个循环退出并给予外部一个返回值,但返回值不是必须的
for
let a = [10, 20, 30, 40, 50]; for i in a.iter() { println!("值为 : {}", i); }
let a = [10, 20, 30, 40, 50]; for i in 0..5 { println!("a[{}] = {}", i, a[i]); }
match语句
enum Option<T> { Some(T), None, } let opt: Option<&str> = Option::None; match opt { Option::Some(something) => { println!("{}", something); }, Option::None => { println!("opt is nothing"); } }
用下划线_表示其余的选项
let t = Some(64); match t { Some(64) => println!("Yes"), _ => println!("No"), }
函数
fn 函数名(参数1: 数据类型, 参数2: 数据类型) -> 返回值类型 { return result; }
注意点
使用关键词 fn,函数体用花括号包围
参数列表用圆括号,名称在前,类型在后,多个参数用逗号(,)分割
返回值用 -> 隔离在参数列表的后面
参数和返回值都是可选的
除了明确使用result返回值,函数最后一行的结果会自动返回
fn 函数名(参数1: 参数类型, 参数2: 参数类型) -> 返回值类型 { ... 100; // 100被作为结果返回 }
注意点
参数和返回值都是可选的
除了明确使用result返回值,函数最后一行的结果会自动返回
闭包函数
结构体
基本语法
struct Site { domain: String, found: u32 }
使用关键词 struct 定义结构体
元组结构体
struct Color(u8, u8, u8); struct Point(f64, f64);
存在的意义是为了处理那些需要定义类型(经常使用)又不想太复杂的简单数据
单元结构体
struct UnitStruct;
结构体可以只作为一种象征而无需任何成员
结构体方法
struct Rectangle { width: u32, height: u32, } impl Rectangle { fn area(&self) -> u32 { self.width * self.height } fn wider(&self, rect: &Rectangle) -> bool { self.width > rect.width } }
类似C++类函数
Rust不是面向对象的语言,没有类的概念
结构体方法的第一个参数是&self,相当于C++的this指针
可以有多个 impl 代码块
结构体关联函数
struct Rectangle { width: u32, height: u32, } impl Rectangle { fn create(width: u32, height: u32) -> Rectangle { Rectangle { width, height } } }
类似于C++的类静态函数
关联函数没有 &self 参数
不依赖实例,但是使用它需要声明是在哪个 impl 块中的
适合用来初始化,作为构造函数使用
枚举类
enum Book { Papery, Electronic } let book = Book::Papery;
使用enum关键词
类似c语言的 enum
enum Book { Papery(u32), Electronic(String), } let book = Book::Papery(1001); let ebook = Book::Electronic(String::from("url://..."));
特定
可以为枚举类成员添加元组属性描述
初始化时,需要同时指定属性
enum Book { Papery { index: u32 }, Electronic { url: String }, } let book = Book::Papery{index: 1001};
可以为属性命名
虽然可以如此命名,但请注意,并不能像访问结构体字段一样访问枚举类绑定的属性,是否方法如下
enum Book { Papery {index: u32}, Electronic {url: String}, } let book = Book::Papery{index: 1001}; let ebook = Book::Electronic{url: String::from("url...")}; match book { Book::Papery { index } => { println!("Papery book {}", index); }, Book::Electronic { url } => { println!("E-book {}", url); } }
Slice(切片)类型
字符串切片
let s = String::from("broadcast"); let part1 = &s[0..5]; let part2 = &s[5..9]; println!("{}={}+{}", s, part1, part2);
非字符串切片
fn main() { let arr = [1, 3, 5, 7, 9]; let part = &arr[0..3]; for i in part.iter() { println!("{}", i); } }
区间[min, ..., max],如果min等于最小的区间,则可以省略,如果max等于最大的区间,也可以省略
[ , max]
[min, ]
[ , ]
特性(trait)
类似于Java的接口
用关键词trait定义特性
trait Descriptive { fn describe(&self) -> String; }
用关键词Descriptive实现特性
struct Person { name: String, age: u8 } impl Descriptive for Person { fn describe(&self) -> String { format!("{} {}", self.name, self.age) } }
同一个类可以实现多个特性,每个 impl 块只能实现一个
与接口的不同点:接口只能规范方法而不能定义方法,但特性可以定义方法作为默认方法
特性做参数
fn output(object: impl Descriptive) { println!("{}", object.describe()); } fn output<T: Descriptive>(object: T) { println!("{}", object.describe()); } fn output_two<T: Descriptive>(arg1: T, arg2: T) { println!("{}", arg1.describe()); println!("{}", arg2.describe()); } fn notify(item: impl Summary + Display) fn notify<T: Summary + Display>(item: T)
复杂的实现关系可以使用 where 关键字简化
fn some_function<T: Display + Clone, U: Clone + Debug>(t: T, u: U) 可以简化成: fn some_function<T, U>(t: T, u: U) -> i32 where T: Display + Clone, U: Clone + Debug
特性做返回值
特性做返回值只接受实现了该特性的对象做返回值且在同一个函数中所有可能的返回值类型必须完全一样
fn some_function(bool bl) -> impl Descriptive { if bl { return A {}; } else { return B {}; //错误,只能返回同一种trait } }
泛型
struct Point<T> { x: T, y: T, } impl<T> Point<T> { fn x(&self) -> &T { &self.x } } fn main() { let p = Point { x: 1, y: 2 }; println!("p.x = {}", p.x()); }
线程
Rust 中通过 std::thread::spawn 函数创建新线程
use std::thread; use std::time::Duration; fn spawn_function() { for i in 0..5 { println!("spawned thread print {}", i); thread::sleep(Duration::from_millis(1)); } } fn main() { thread::spawn(spawn_function); for i in 0..3 { println!("main thread print {}", i); thread::sleep(Duration::from_millis(1)); } }
join方法
join 方法可以使主线程等待子线程运行结束后再停止运行程序
use std::thread; fn main() { let s = "hello"; let handle = thread::spawn(|| { println!("{}", s); }); handle.join().unwrap(); }
move 强制所有权迁移
use std::thread; fn main() { let s = "hello"; let handle = thread::spawn(move || { println!("{}", s); }); handle.join().unwrap(); }
消息传递
Rust 中一个实现消息传递并发的主要工具是通道(channel),通道有两部分组成,一个发送者(transmitter)和一个接收者(receiver)
use std::thread; use std::sync::mpsc; fn main() { let (tx, rx) = mpsc::channel(); thread::spawn(move || { let val = String::from("hi"); tx.send(val).unwrap(); }); let received = rx.recv().unwrap(); println!("Got: {}", received); }
数据类型
基本数据
所有整数类型,例如 i32 、 u32 、 i64 等
布尔类型 bool,值为 true 或 false
所有浮点类型,f32 和 f64
字符类型 char
仅包含以上类型数据的元组(Tuples)
基本数据类型存储在栈中
复合类型
数组
用一对 [ ] 包括的同类型数据
let a = [1, 2, 3, 4, 5]; // a 是一个长度为 5 的整型数组 let b = ["January", "February", "March"]; // b 是一个长度为 3 的字符串数组 let c: [i32; 5] = [1, 2, 3, 4, 5]; // c 是一个长度为 5 的 i32 数组 let d = [3; 5]; // 等同于 let d = [3, 3, 3, 3, 3];
元组
用一对 ( ) 包括的一组数据,可以包含不同种类的数据
let tup: (i32, f64, u8) = (500, 6.4, 1); // tup.0 等于 500 // tup.1 等于 6.4 // tup.2 等于 1 let (x, y, z) = tup; // y 等于 6.4
集合(Collection)
向量(Vector)
向量是线性表,在 Rust 中的表示是 Vec<T>
get 方法的返回值是 Option 枚举类,有可能为空
fn main() { let mut v = vec![1, 2, 4, 8]; println!("{}", match v.get(0) { Some(value) => value.to_string(), None => "None".to_string() }); }
如果能够保证取值的下标不会超出向量下标取值范围,也可以使用数组取值语法
fn main() { let v = vec![1, 2, 4, 8]; println!("{}", v[1]); }
字符串
let string = String::new(); // 基础类型转换成字符串: let one = 1.to_string(); // 整数到字符串 let float = 1.3.to_string(); // 浮点数到字符串 let slice = "slice".to_string(); // 字符串切片到字符串 // 包含 UTF-8 字符的字符串: let hello = String::from("你好"); // 字符串追加: let s1 = String::from("Hello, "); let s2 = String::from("world!"); let s3 = s1 + &s2; let s = s1 + "-" + &s2 + "-" + &s3; let s = format!("{}-{}-{}", s1, s2, s3); // 字符串长度 let len = s.len(); // 统计字符数量 let len = s.chars().count(); // 从字符串中取单个字符: let a = s.chars().nth(2); println!("{:?}", a);
字符统一采用utf8编码
中文是 UTF-8 编码的,每个字符长 3 字节
nth 函数是从迭代器中取出某值的方法,请不要在遍历中这样使用!因为 UTF-8 每个字符的长度不一定相等!
映射表(Map)
应用最普遍的就是键值散列映射表(Hash Map)
use std::collections::HashMap; fn main() { let mut map = HashMap::new(); map.insert("color", "red"); map.insert("size", "10 m^2"); map.entry("color").or_insert("red"); for p in map.iter() { println!("{:?}", p); } }
or_insert: 确认元素不存在则插入
结构体数据
特点
变量
声明变量, let关键词
Basic和js也使用let关键词定义变量
可变变量
不可变变量, mut关键词
可以重新定义同名变量,此时原变量被shadow(隐藏)
函数式编程
类似于c,函数编程
用结构体函数和结构体方法,实现了类的封装的概念
通过traits实现接口,但是没有继承的概念
支持泛型
数据的生命周期
静态生命周期
static 关键词
从程序运行开始到程序运行结束
局部生命周期
从定义开始,直到代码块结束,或者被借用
所有权
严格要求程序员显示管理堆数据的所有权,杜绝了内存溢出、野指针、无主内存块(内存泄露)
三条规则
Rust 中的每个值都有一个变量,称为其所有者
一次只能有一个所有者
当所有者不在程序运行范围时,该值将被删除
(堆) 变量与数据交互方式
移动(Move)
let s1 = String::from("hello"); let s2 = s1; println!("{}, world!", s1); // 错误!s1 已经失效
s1的数据所有权被转移到s2,此时不能再使用s1 
克隆(Clone)
let s1 = String::from("hello"); let s2 = s1.clone(); println!("s1 = {}, s2 = {}", s1, s2); //正确,
克隆会复制完整的数据部分,产生2部分数据的所有权,赋值后,变量仍然可以继续使用
涉及函数的所有权机制
向函数传递参数的所有权机制
如果将变量当作参数传入函数,那么它和移动的效果是一样的
fn main() { let s = String::from("hello"); // s 被声明有效 takes_ownership(s); // s 的值被当作参数传入函数 // 所以可以当作 s 已经被移动,从这里开始已经无效 let x = 5; // x 被声明有效 makes_copy(x); // x 的值被当作参数传入函数 // 但 x 是基本类型,依然有效 // 在这里依然可以使用 x 却不能使用 s } // 函数结束, x 无效, 然后是 s. 但 s 已被移动, 所以不用被释放 fn takes_ownership(some_string: String) { // 一个 String 参数 some_string 传入,有效 println!("{}", some_string); } // 函数结束, 参数 some_string 在这里释放 fn makes_copy(some_integer: i32) { // 一个 i32 参数 some_integer 传入,有效 println!("{}", some_integer); } // 函数结束, 参数 some_integer 是基本类型, 无需释放
函数返回值的所有权机制
被当作函数返回值的变量所有权将会被移动出函数并返回到调用函数的地方,而不会直接被无效释放
fn main() { let s1 = gives_ownership(); // gives_ownership 移动它的返回值到 s1 let s2 = String::from("hello"); // s2 被声明有效 let s3 = takes_and_gives_back(s2); // s2 被当作参数移动, s3 获得返回值所有权 } // s3 无效被释放, s2 被移动, s1 无效被释放. fn gives_ownership() -> String { let some_string = String::from("hello"); // some_string 被声明有效 return some_string; // some_string 被当作返回值移动出函数 } fn takes_and_gives_back(a_string: String) -> String { // a_string 被声明有效 a_string // a_string 被当作返回值移出函数 }
引用和租借
"引用"是变量的间接访问方式
类似于C++语言的引用
引用不会获得值的所有权, 只能租借(Borrow)值的所有权
引用不具有所有权,即使它租借了所有权,它也只享有使用权,不能修改
fn main() { let s1 = String::from("run"); let s2 = &s1; println!("{}", s2); s2.push_str("oob"); // 错误,禁止修改租借的值 println!("{}", s2); let s3 = s1; println!("{}", s2); // 错误,s1的所有权被s3租界,此时不能再使用 s2 = &s3; // 重新从 s3 租借所有权 println!("{}", s2); // 又可以使用了 }

垂悬引用(Dangling References),即引用被释放的变量
fn main() { let reference_to_nothing = dangle(); } fn dangle() -> &String { let s = String::from("hello"); &s } 伴随着 dangle 函数的结束,其局部变量的值本身没有被当作返回值,被释放了。但它的引用却被返回,这个引用所指向的值已经不能确定的存在,故不允许其出现
脚手架
cargo
cargo new project
在当前文件夹下面创建新的Rust项目
cargo check
检查源代码的语法
cargo run
编译并运行程序
cargo update
更新项目依赖的包