🎊

Rust

 
 
环境搭建
rustc -V
新建项目
cargo new greeting cd ./greeting cargo build cargo run
VSCode 配置
# tasks.json { "version": "2.0.0", "tasks": [ { "label": "build", "type": "shell", "command": "cargo", "args": [ "build" ] } ] } # launch.json [windows] { "version": "0.2.0", "configurations": [ { "name": "(Windows) 启动", "preLaunchTask": "build", "type": "cppvsdbg", "request": "launch", "program": "${workspaceFolder}/target/debug/${workspaceFolderBasename}.exe", "args": [], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], "externalConsole": false }, { "name": "(gdb) 启动", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/target/debug/${workspaceFolderBasename}.exe", "args": [], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], "externalConsole": false, "MIMode": "gdb", "miDebuggerPath": "这里填GDB所在的目录", "setupCommands": [ { "description": "为 gdb 启用整齐打印", "text": "-enable-pretty-printing", "ignoreFailures": true } ] } ] } # launch.json [linux] { "version": "0.2.0", "configurations": [ { "name": "Debug", "type": "gdb", "preLaunchTask": "build", "request": "launch", "target": "${workspaceFolder}/target/debug/${workspaceFolderBasename}", "cwd": "${workspaceFolder}" } ] } # launch.json [mac] { "version": "0.2.0", "configurations": [ { "name": "(lldb) 启动", "type": "cppdbg", "preLaunchTask": "build", "request": "launch", "program": "${workspaceFolder}/target/debug/${workspaceFolderBasename}", "args": [], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], "externalConsole": false, "MIMode": "lldb" } ] }
命令行手动编译执行
# runoob.rs fn main() { let a = 12; println!("a is {}", a); } rustc runoob.rs ./runoob
 

语法

注释

// 这是第一种注释方式 /* 这是第二种注释方式 */ /*  * 多行注释  * 多行注释  * 多行注释  */ /// 文档注释 /// ``` /// let x = add(1, 2); /// /// ```

变量

// 变量 let a = 123; let mut a = 123; const a: i32 = 123; //重影 shadowing 指变量的名称可以被重新使用的机制 let x = 5; let x = x + 1; let x = x * 2; // x == 12 let mut s = "123"; s = s.len(); // 错误!不能给字符串变量赋整型值

数据类型

//数据类型 //整数 有符号 i8, i16, i32 等,无符号 u8, u16, u32等 //进制 十六进制 0x, 八进制 0o, 二进制 0b, 字节(u8)b'A' //浮点数 f32, f64 //数学运算+-*/% //布尔型 bool, true false //字符型 char 'A' //复合类型 元组 () 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 //复合类型 数组 [] let c: [i32; 5] = [1, 2, 3, 4, 5]; // c 是一个长度为 5 的 i32 数组 let d = [3; 5]; // 等同于 let d = [3, 3, 3, 3, 3]; let first = a[0]; // 数组访问 a[0] = 123; // 错误:数组 a 不可变 let mut a = [1, 2, 3]; a[0] = 4; // 正确

函数、表达式、条件、循环

//函数 fn add(a: i32, b: i32) -> i32 { return a + b; } //表达式块 let x = 5; let y = { let x = 3; x + 1 //<-注意这里没有分号,意味着是表达式而不是语句 }; //条件语句 let number = if a > 0 { 1 } else { -1 }; if a > 0 { b = 1; } else if a < 0 { b = -1; } else { b = 0; } //循环语句 let mut i = 0; while i < 10 { // 循环体 i += 1; } let a = [10, 20, 30, 40, 50]; for i in a.iter() {} for i in 0..5 {} loop { let ch = s[i]; if ch == 'O' { break; } println!("\'{}\'", ch); i += 1; }

所有权机制

变量与值的所有权

//变量与值的所有权 //1. 移动(移动的是对于值的所有权) let s1 = String::from("hello"); let s2 = s1; println!("{}, world!", s1); // 错误!s1 已经失效 //2. 克隆 let s2 = s1.clone();

函数相关的所有权机制

//函数相关的所有权机制 //1. 函数参数的所有权 fn main() {     let s = String::from("hello"); // s 被声明有效     takes_ownership(s);     // s 的值被当作参数传入函数,移动给some_string     // 所以可以当作 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 是基本类型, 无需释放 //2. 函数返回值的所有权 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 被当作返回值移出函数 }

引用和租借

//引用和租借 //引用不会获得值的所有权。 //引用只能租借(Borrow)值的所有权。 //引用本身也是一个类型并具有一个值,这个值记录的是别的值所在的位置,但引用不具有所指值的所有权 let s1 = String::from("hello"); let s2 = &s1; let s3 = s1; println!("{}", s2); // 错误!s2 租借的 s1 已经将所有权移动到 s3,所以 s2 将无法继续租借使用 s1 的所有权 s2 = &s3; // 重新从 s3 租借所有权 println!("{}", s2); // 正确! s2.push_str("oob"); // 错误!禁止修改租借的值 let mut s1 = String::from("run"); // s1 是可变的 let s2 = &mut s1; // s2 是可变的引用 s2.push_str("oob"); // 正确!可变引用 //可变引用不允许多重引用,但不可变引用可以 let mut s = String::from("hello"); let r1 = &mut s; let r2 = &mut s; println!("{}, {}", r1, r2); //错误!多重可变引用了 s //垂悬引用(Dangling References) fn main() { let reference_to_nothing = dangle(); } fn dangle() -> &String { let s = String::from("hello"); &s //错误!局部变量的值本身没有被当作返回值,被释放了。但它的引用却被返回 }

切片类型

//字符串 let s = String::from("broadcast"); let part1 = &s[0..5]; //..y 等价于 0..y //x.. 等价于位置 x 到数据结束 //.. 等价于位置 0 到结束 let mut s = String::from("runoob"); let slice = &s[0..3]; s.push_str("yes!"); // 错误!s 被部分引用,禁止更改其值。 let s = "hello"; // s 是 &str 类型 let s2 = &s1[..]; //将 String 转换成 &str //数组也支持切片

结构体

//定义 struct Site { domain: String, name: String, nation: String, found: u32 } //实例 let domain = String::from("www.runoob.com"); let runoob = Site { domain, //如果有字段名称和现存变量名称一样的,可以简化 name: String::from("RUNOOB"), nation: String::from("China"), found: 2013 }; let site = Site { domain: String::from("www.runoob.com"), name: String::from("RUNOOB"), ..runoob }; let Point { x: a, y: b } = p; let Point { a, b } = p; //元组结构体 struct Color(u8, u8, u8); let black = Color(0, 0, 0); //打印结构体 #[derive(Debug)] //导入调试库 println!("site is {:?}", site); // {:?} 扁平输出, {:#?} 树形缩进输出 match p { Point { x, y: 0 } => println!("On the x axis at {}", x), Point { x: 0, y } => println!("On the y axis at {}", y), Point { x, y } => println!("On neither axis: ({}, {})", x, y), Point { x, .. } => println!("x is {}", x), } //结构体方法 impl Rectangle { fn area(&self) -> u32 { //第一个参数必须是 &self,不需要声明类型,因为self是关键字 self.width * self.height } fn wider(&self, rect: &Rectangle) -> bool { self.width > rect.width } } //结构体 impl 块可以写几次,效果相当于它们内容的拼接! impl Rectangle { fn create(width: u32, height: u32) -> Rectangle { Rectangle { width, height } } } let rect1 = Rectangle { width: 30, height: 50 }; let rect2 = Rectangle::create(30, 50); println!("{}", rect1.wider(&rect2)); //单元结构体 //结构体可以只作为一种象征而无需任何成员: struct UnitStruct;

枚举

enum Book { Papery, Electronic } let book = Book::Papery; println!("{:?}", book); //属性描述 //1. 元组语法 enum Book { Papery(u32), Electronic(String), } let book = Book::Papery(1001); let ebook = Book::Electronic(String::from("url://...")); //2. 结构体语法 enum Book { Papery { index: u32 }, Electronic { url: String }, } let book = Book::Papery{index: 1001}; //3. 混合 enum Book { Papery(u32), Electronic {url: String}, } let book = Book::Papery(1001); //match match book { Book::Papery(i) => { //元组语法 println!("{}", i); }, Book::Electronic { url } => { //结构体语法 println!("{}", url); } } let t = "abc"; match t { "abc" => println!("Yes"), _ => {}, } //标准库中的 Option 枚举类 enum Option<T> { Some(T), None, //Rust 在语言层面彻底不允许空值 null,但 null 还是有用的。所以标准库引入了None } let opt = Option::Some("Hello"); //自动推断为 &str let opt: Option<&str> = Option::None; // 初始值None 必须手动指明类型 match opt { Option::Some(something) => { println!("{}", something); }, Option::None => { //必须处理None println!("opt is nothing"); } } // Option 库是 Rust 编译器默认引入的,在使用时可以省略 Option:: let t = Some(64); match t { Some(64) => println!("Yes"), _ => println!("No"), } //if let 语法糖用来简化 match if let Some(index) = t { println!("index {0}", index); }

组织管理

  • 箱 Create
    • 二进制文件或库文件,存在于包中
  • 包 Package
    • 包必须由一个 Cargo.toml 文件来管理,该文件描述了包的基本信息以及依赖项。
      一个包最多包含一个库"箱",可以包含任意数量的二进制"箱",但是至少包含一个"箱"(不管是库还是二进制"箱")
  • 模块 Module
    • //类似文件目录 mod nation { pub mod government { //访问权默认私有,请使用 pub 表示公有 pub fn govern() {} } mod congress { fn legislate() {} } mod court { fn judicial() { super::congress::legislate(); //内部可以私有访问 } } } //使用模块里的函数,以路径的形式调用 crate::nation::government::govern(); //绝对路径从 crate 关键字开始描述。 nation::government::govern(); //相对路径从 self 或 super 关键字或一个标识符开始描述。 //模块中的结构体以及其字段,需要 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), 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!("I'd like {} toast please", meal.toast); } //模块中的枚举类的字段不需要声明 pub mod SomeModule { pub enum Person { King { name: String }, Quene } } //文件名当模块名称 // second_module.rs pub fn message() -> String { String::from("This is the 2nd module.") } // main.rs mod second_module; fn main() { println!("This is the main module."); println!("{}", second_module::message()); } //use 关键字能够将模块标识符引入当前作用域 mod nation { pub mod government { pub fn govern() {} } pub use government::govern; } use crate::nation::government::govern; use crate::nation::govern as nation_govern; fn main() { govern(); nation_govern(); nation::govern(); } //标准库都是默认导入,可以使用use简化路径 use std::f64::consts::PI;

错误处理

//不可恢复错误 fn main() { panic!("error occured"); //不可恢复错误 println!("Hello, Rust"); } //可恢复错误 enum Result<T, E> { Ok(T), Err(E), } fn main() { let f = File::open("hello.txt"); match f { Ok(file) => { println!("File opened successfully."); }, Err(err) => { match e.kind() { //kind 方法处理错误类型 io::ErrorKind::NotFound => { println!("No such file"); }, _ => { println!("Cannot read the file"); } } println!("Failed to open the file."); } } //if let 简化 if let Ok(file) = f { println!("File opened successfully."); } else { println!("Failed to open the file."); } //将可恢复错误转化为不可恢复错误 let f1 = File::open("hello.txt").unwrap(); let f2 = File::open("hello.txt").expect("Failed to open."); } //错误传递 fn f(i: i32) -> Result<i32, bool> { if i >= 0 { Ok(i) } else { Err(false) } } fn g(i: i32) -> Result<i32, bool> { let t = f(i); return match t { Ok(i) => Ok(i), Err(b) => Err(b) }; } fn g(i: i32) -> Result<i32, bool> { let t = f(i)?; //可以在 Result 对象后添加 ? 操作符将同类的 Err 直接传递出去 Ok(t) // 因为确定 t 不是 Err, t 在这里已经是 i32 类型 } //? 符的实际作用是将 Result 类非异常的值直接取出,如果有异常就将异常 Result 返回出去。 //所以,? 符仅用于返回值类型为 Result<T, E> 的函数 //其中 E 类型必须和 ? 所处理的 Result 的 E 类型一致。

泛型

fn max<T>(array: &[T]) -> T { ... } struct Point<T> {     x: T,     y: T } impl<T> Point<T> { fn x(&self) -> &T { &self.x } } impl Point<f64> { //可以为其中的一种泛型添加方法 fn x(&self) -> f64 { self.x } } impl<T, U> Point<T, U> { //impl 块本身的泛型并没有阻碍其内部方法具有泛型的能力: fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> { Point { x: self.x, y: other.y, } } } enum Option<T> { Some(T), None, } enum Result<T, E> { Ok(T), Err(E), } //特性trait(类似于接口) trait Descriptive { fn describe(&self) -> String; } struct Person { name: String, age: u8 } //同一个类可以实现多个特性,每个 impl 块只能实现一个 impl Descriptive for Person { fn describe(&self) -> String { format!("{} {}", self.name, self.age) } } //默认特性,提供默认实现 trait Descriptive { fn describe(&self) -> String { String::from("[Object]") } } //特性做参数(下面两者等效) fn output(object: impl Descriptive) { println!("{}", object.describe()); } fn output<T: Descriptive>(object: T) { println!("{}", object.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 person() -> impl Descriptive { Person { name: String::from("Cali"), age: 24 } } //特性约束的泛型 struct A<T> {} impl<T: B + C> A<T> { fn d(&self) {} }

生命周期

&i32 // 常规引用 &'a i32 // 含有生命周期注释的引用 &'a mut i32 // 可变型含有生命周期注释的引用 //错误!不能返回,因为参数的返回后被释放,参数的生命周期短于返回值的生命周期 fn longer(s1: &str, s2: &str) -> &str { if s2.len() > s1.len() { s2 } else { s1 } } //正确!可以返回,因为返回值的生命周期和参数的生命周期一致 fn longer<'a>(s1: &'a str, s2: &'a str) -> &'a str { if s2.len() > s1.len() { s2 } else { s1 } } //结构体中使用字符串切片引用,需要声明生命周期 struct Str<'a> { content: &'a str } //impl 生命周期 impl<'a> Str<'a> { fn get_content(&self) -> &str { self.content } }

文件与 IO

fn main() { //1. 命令行参数 let args = std::env::args(); println!("{:?}", args); for arg in args { println!("{}", arg); } //2. 命令行输入 let mut str_buf = String::new() stdin().read_line(&mut str_buf).expect("Failed to read line."); } //文件读取 use std::fs; let text = fs::read_to_string("D:\\text.txt").unwrap(); //读取为字符串 let content = fs::read("D:\\text.txt").unwrap(); //读取为二进制 u8 //流式读取 let mut buffer = [0u8; 5]; let mut file = fs::File::open("D:\\text.txt").unwrap(); file.read(&mut buffer).unwrap(); println!("{:?}", buffer); file.read(&mut buffer).unwrap(); println!("{:?}", buffer); //文件写入 //1. 一次性写入 fs::write("D:\\text.txt", "FROM RUST PROGRAM").unwrap(); //2. 流式写入 let mut file = File::create("D:\\text.txt").unwrap(); file.write(b"FROM RUST PROGRAM").unwrap(); //3. 追加写入 let mut file = OpenOptions::new().append(true).open("D:\\text.txt")?; file.write(b" APPEND WORD")?; //在文件D:\\text.txt末尾继续追加字符串 let mut file = OpenOptions::new().read(true).write(true).open("D:\\text.txt")?; file.write(b"COVER")?; //OpenOptions 可以控制读写模式

集合

向量

let vector: Vec<i32> = Vec::new(); // 创建类型为 i32 的空向量 let vector = vec![1, 2, 4, 8]; // 通过数组创建向量 let mut vector = vec![1, 2, 4, 8]; vector.push(16); let mut v2: Vec<i32> = vec![16, 32, 64]; vector.append(&mut v2); //遍历 for i in &vector { println!("{}", i); } //遍历时改值 let mut v = vec![100, 32, 57]; for i in &mut v { *i += 50; } //取值 vector[0] // 或 vector.get(0) //向量的长度无法从逻辑上推断,get 方法无法保证一定取到值 //所以 get 方法的返回值是 Option 枚举类,有可能为空。 match vector.get(0) { Some(value) => value.to_string(), None => "None".to_string() }

字符串

let string = String::new(); let one = 1.to_string(); // 整数到字符串 let float = 1.3.to_string(); // 浮点数到字符串 let slice = "slice".to_string(); // 字符串切片到字符串 let hello = String::from("你好"); let mut s = String::from("run"); s.push_str("oob"); // 追加字符串切片 s.push('!'); // 追加字符 let s1 = String::from("Hello, "); let s2 = String::from("world!"); let s3 = s1 + &s2; // 拼接 let s = format!("{}-{}-{}", s1, s2, s3); //format! 宏 let len = s.len(); //字符串长度 "你好".len() //长度为6,因为字符串是UTF8编码,每个字符长 3 字节 let len = s.chars().count(); //字符数量 //遍历字符串 for c in s.chars() { println!("{}", c); } let a = s.chars().nth(2); // 取第二个字符 let sub = &s[0..2]; //截取字符串,可能肢解一个 UTF-8 字符

映射表

use std::collections::HashMap; let mut map = HashMap::new(); map.insert("color", "red"); println!("{}", map.get("color").unwrap()); for p in map.iter() { println!("{:?}", p); //("color", "red") } map.entry("color").or_insert("red");//如果没有键为 "color" 的键值对就添加它并设定值为 "red",否则将跳过 let mut map = HashMap::new(); map.insert(1, "a"); if let Some(x) = map.get_mut(&1) { *x = "b"; //如果确定键存在,可以快速修改对应的值 }

声明式宏 macro_rules!

#[macro_export] macro_rules! vec { ( $( $x:expr ),* ) => { { let mut temp_vec = Vec::new(); $( temp_vec.push($x); )* temp_vec } }; }

过程宏

过程宏有三种类型(自定义 derive、属性宏、函数宏)
创建演示工程
cargo new hello_macro cd hello_macro/ touch src/lib.rs cargo new hello_macro_derive --lib # 目录结构如下 hello_macro ├── Cargo.toml ├── src │ ├── main.rs │ └── lib.rs └── hello_macro_derive ├── Cargo.toml ├── src └── lib.rs
主工程
// src/lib.rs pub trait HelloMacro { fn hello_macro(); } // src/main.rs use hello_macro::HelloMacro; use hello_macro_derive::HelloMacro; #[derive(HelloMacro)] struct Sunfei; #[derive(HelloMacro)] struct Sunface; fn main() { Sunfei::hello_macro(); Sunface::hello_macro(); } // Cargo.toml [dependencies] hello_macro_derive = { path = "../hello_macro/hello_macro_derive" } # 也可以使用下面的相对路径 # hello_macro_derive = { path = "./hello_macro_derive" } //////////////////////////////////////////////////// ////////////src/main.rs等价于下面这段代码//////////// //////////////////////////////////////////////////// use hello_macro::HelloMacro; struct Sunfei; impl HelloMacro for Sunfei { fn hello_macro() { println!("Hello, Macro! My name is Sunfei!"); } } struct Sunface; impl HelloMacro for Sunface { fn hello_macro() { println!("Hello, Macro! My name is Sunface!"); } } fn main() { Sunfei::hello_macro(); }
宏工程
// hello_macro_derive/Cargo.toml [lib] proc-macro = true [dependencies] syn = "1.0" quote = "1.0" // hello_macro_derive/src/lib.rs extern crate proc_macro; use proc_macro::TokenStream; use quote::quote; use syn; #[proc_macro_derive(HelloMacro)] pub fn hello_macro_derive(input: TokenStream) -> TokenStream { // 基于 input 构建 AST 语法树 let ast = syn::parse(input).unwrap(); // 构建特征实现代码 impl_hello_macro(&ast) } fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream { let name = &ast.ident; let gen = quote! { impl HelloMacro for #name { fn hello_macro() { println!("Hello, Macro! My name is {}!", stringify!(#name)); } } }; gen.into() } /////////////////////////////////////// /////////////ast的结构如下////////////// /////////////////////////////////////// DeriveInput { // --snip-- ident: Ident { ident: "Sunfei", span: #0 bytes(95..103) }, data: Struct( DataStruct { struct_token: Struct, fields: Unit, semi_token: Some( Semi ) } ) }
类属性宏(Attribute-like macros)
// 使用 #[route(GET, "/")] fn index() { // 定义 #[proc_macro_attribute] pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
类函数宏(Function-like macros)
// 使用 let sql = sql!(SELECT * FROM posts WHERE id=1); // 定义 #[proc_macro] pub fn sql(input: TokenStream) -> TokenStream {