【Rust基础】语法知识
系列综述:
💞目的:本系列是个人学习Rust语言整理的,整理期间苛求每个知识点,平衡理解简易度与深入程度。
🥰来源:材料主要源于b站的Rust中文社群线上学习室和菜鸟教程
进行的,每个知识点的修正和深入主要参考各平台大佬的文章,其中也可能含有少量的个人实验自证。
🤭结语:如果有帮到你的地方,就点个赞和关注一下呗,谢谢🎈🎄🌷!!!
文章目录
😊点此到文末惊喜↩︎
语法
变量variables
- 变量声明
- 不可变绑定:
let
将值和变量名进行绑定,声明同时必须初始化
// 不可变绑定 let x = 5;// 绑定同时必须初始化 x = 10; // error: 绑定后不能改变 // 强类型的自动推导 let x = 1.000;// 编译器会推断为f32 let x : f64 = 1.000;// 类型注解
- 可变绑定:使用
mut
表示可变绑定
// 普通的mut可变 let mut x = 5; x = 10;
- 重新绑定(重影):变量名称可以被重新使用的机制
// 变量遮蔽的继承可变 let x = 5; // warning:未使用的符号 let x = 10;// 可以通过let对同一符号名进行重新绑定
- 不可变绑定:
- 注意:
- Rust 是
强类型语言
,能类型推导和进行类型安全检查 - 声明变量最好加
类型注解
,没有注解Rust会进行类型推断,可能不符合预期 - Rust处于
内存安全
考虑,不允许使用或捕获任何未初始化的变量 位置表达式 = 值表达式
,Rust没有左右值的概念,左边表达式返回内存地址,右边表达式返回值
- Rust 是
- 常量
- 常量必须加类型,因为局部的类型推断可能出现问题
- 在编译器进行了类型检查的文本替换,占用代码段或者说不占用内存空间,运行时常量值会被内联到使用的位置
const NUMBER:i32 = 3;
- 默认不可变的好处
- 代码逻辑的干净清爽。人们总是默认使用简短的不可变语法,所以使得代码逻辑更加简单
- 多线程编程中的参数传递。假如语法层面保证了一个值的不可变,就不需要锁保护
- Rust没有三元表达符:避免N元嵌套带来的函数语义复杂性,但可以直接使用if-else作为表达式r
- 表达式可以更容易的进行静态分析,Rust通过表达式的类型检查保证数据安全从而保证数据安全性
- 分号的意义
- 有分号语句是一个statement,如果分号后面没有东西会补一个单元类型做return
- 无分号语句是一个表达式,具有返回值
数据类型
- 基本数据类型
位长度 | 有符号 | 无符号 |
---|---|---|
8-bit | i8 | u8 |
16-bit | i16 | u16 |
32-bit | i32 | u32 |
64-bit | i64 | u64 |
128-bit | i128 | u128 |
arch | isize | usize |
- 浮点数
fn main() {let x = 2.0; // 默认浮点数类型推导为f64let y: f32 = 3.0; // f32 }
- Rust 不支持 ++ 和 --,因为这两个运算符出现在变量的前后会影响代码可读性,减弱了开发者对变量改变的意识能力。
- 布尔型:用 bool 表示,值只能为 true 或 false。
- Rust 中字符串和字符都必须使用 UTF-8 编码,否则编译器会报错。
- 元组用一对 ( ) 包括的一组数据,可以包含不同种类的数据
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
- 数组用一对 [ ] 包括的同类型数据。
// a 是一个长度为 5 的整型数组 let a = [1, 2, 3, 4, 5]; // b 是一个长度为 3 的字符串数组 let b = ["January", "February", "March"]; // c 是一个长度为 5 的 i32 数组 let c: [i32; 5] = [1, 2, 3, 4, 5];// [类型;长度] // 等同于 let d = [3, 3, 3, 3, 3]; let d = [3; 5]; // 数组访问 let first = a[0]; let second = a[1]; a[0] = 123; // 错误:数组 a 不可变 let mut a = [1, 2, 3]; a[0] = 4; // 正确
注释
- 类型
/// 常用作文档的开头// 这是第一种注释方式/* 这是第二种注释方式 *// 多行注释* 多行注释* 多行注释*/
函数functions
- 函数的基本声明
fn 函数名 ( 参数 ) {函数体}
- Rust是编译型语言,可以先解释后声明
- 函数形参必须指定类型
// 主函数 fn main() {call_me(5); } // 子函数 fn call_me(num: u32) {// 参数必须类型声明for i in 0..num {println!("Ring! Call number {}", i + 1);} }fn call_me(num: u32)-> i32 {// ->表示返回值类型num }
- Rust 无法自动推导返回值类型,若没有明确声明函数返回值的类型,函数将被认为是"纯过程"
分支if
- if
if condition { block 1 } else { block 2 }
- if后面的条件表达式不用加()
- if的语句块必须加{ }
if a > b {// 没有括号a } else {b }
- Rust不支持三元运算符,但是可以使用if表达式
let number = if a > 0 { 1 } else { -1 };
循环
- 类型
//基本while循环 let mut i = 0; while i < 10 {// 循环体i += 1; }// 范围for let a = [10, 20, 30, 40, 50]; for i in a.iter() {println!("{}", i); } // 下标for let a = [10, 20, 30, 40, 50]; for i in 0..5 {println!("a[{}] = {}", i, a[i]); } // 无限循环loop let mut i = 0; loop {let ch = s[i];if i == 5 {break;}i += 1; }
切片
- String对象
- 内存结构:存储在栈上,大小固定为三个字。
- 一个指向堆中字符串的指针
- String的容量
- String的长度
- 可以根据需要调整容量,通过push_str()追加字符串
- 保证内部只保存标准的UTF-8文本
- 内存结构:存储在栈上,大小固定为三个字。
- &str
- 定义:针对字符串中的特定部分的引用
- 内存结构:栈上为
切片后字符串在堆的起始地址+长度
,堆上为被引用的字符串 - 应用场景:常用于作为只读形参
- 原则:被部分引用的变量不能通过引用进行原值的修改
切片
和双引号的字符串
都是&str类型
let s = String::from("broadcast"); let part1 = &s[0..5];// 0,1,2,3,4 // 参数只读的打印函数 fn greet(name: &str) {// 类似与const &形参println!("Hello, {}!", name); } // 切片引用不能修改原值 let mut s = String::from("runoob"); let slice = &s[0..3]; s.push_str("yes!"); // 错误
- String和&str的转换
- String 和 str 都支持切片,切片的结果是 &str 类型的数据。
let s1 = String::from("hello"); let s2 = &s1[..];// String 转换成 &str let s3 = s2.tostring();
- 切片(Slice)是对数据值的部分引用。切片变量实际为
片开始指针 + 大小
- 范围运算符
..
,其中x…y 表示 [x, y) 的数学含义..y // 等价于 0..y x.. // 等价于位置 x 到数据结束 .. // 等价于位置 0 到结束 x..y // x到y前闭后开
- 其他线性数据结构也支持切片操作,例如数组
let arr = [1, 3, 5, 7, 9]; let part = &arr[0..3]; for i in part.iter() {println!("{}", i); }
结构体
- 结构体实例
// 直接初始化 let runoob = Site {domain: String::from("www.runoob.com"),name: String::from("RUNOOB"),nation: String::from("China"),found: 2013 };// 初始化后组合 let domain = String::from("www.runoob.com"); let name = String::from("RUNOOB"); let runoob = Site {domain, // 等同于 domain : domain,name, // 等同于 name : name,nation: String::from("China"),traffic: 2013 };
- 结构体属性省略符
..runoob
let site = Site {domain: String::from("www.runoob.com"),name: String::from("RUNOOB"),..runoob };
- 元组结构体
struct Color(u8, u8, u8); struct Point(f64, f64); let black = Color(0, 0, 0); let origin = Point(0.0, 0.0);
- 结构体所有权
- 结构体掌握字段值的所有权,结构体失效会释放所有字段
- 结构体属性占位符
{:#?}
struct Rectangle {width: u32,height: u32, } fn main() {let rect1 = Rectangle { width: 30, height: 50 };println!("rect1 is {:?}", rect1);// 打印结构体的所有属性 }
- 计算一个矩形
- 属性和方法的分离,实例化对象只需要初始化其结构体
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} }fn main() {let rect1 = Rectangle { width: 30, height: 50 };let rect2 = Rectangle { width: 40, height: 20 };println!("{}", rect1.wider(&rect2)); }
枚举类
- 基本使用
// 基本用法 enum Book {Papery, Electronic } let book = Book::Papery; // 枚举类属性增加描述 enum Book {Papery(u32),Electronic(String), } let book = Book::Papery(1001); // 属性命名 enum Book {Papery { index: u32 },Electronic { url: String }, } let book = Book::Papery{index: 1001};
- Option 枚举类(null)
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");}
}
// 输出opt is nothing
if let
if let 匹配值 = 源变量 {语句块1 }else{语句块2 } // 示例 let i = 0; if let 0 = i {println!("zero"); } // 输出zero
- 模块库
// 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()); } //输出This is the main module. This is the 2nd module.
use
引入模块,as
避免局部模块重名mod nation {pub mod government {pub fn govern() {}}pub use government::govern; }fn main() {nation::govern(); }
错误处理
- 错误类型
- 可恢复错误:用 Result<T, E> 类来处理。例如文件占用,可以通过等待解决
- 不可恢复错误:编程逻辑错误,使用 panic! 宏来处理。
- panic! 宏的调用
- 第一行:输出了 panic! 宏调用的位置以及其输出的错误信息
- 第二行"通过
RUST_BACKTRACE=1
环境变量运行以显示回溯"
fn main() {panic!("error occured");println!("Hello, Rust");
}
// 输出
thread 'main' panicked at 'error occured', src\\main.rs:3:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
- 可恢复错误
- 在 Rust 中通过 Result<T, E> 枚举类作返回值来进行异常表达
use std::fs::File;fn main() {let f = File::open("hello.txt");match f {Ok(file) => {println!("File opened successfully.");},Err(err) => {println!("Failed to open the file.");}} }
- 可恢复错误按不可恢复错误处理
// 在 Result 为 Err 时调用 panic! 宏 let f1 = File::open("hello.txt").unwrap(); // 在 Result 为 Err 时调用 panic! 宏,并且可以输出一段指定信息 let f2 = File::open("hello.txt").expect("Failed to open.");
- 功能与异常的独立
?
符仅用于返回值类型为 Result<T, E> 的函数,将 Result 类非异常的值直接取出,如果有异常就将异常 Result 返回出去
use std::io; use std::io::Read; use std::fs::File;fn read_text_from_file(path: &str) -> Result<String, io::Error> {let mut f = File::open(path)?;let mut s = String::new();f.read_to_string(&mut s)?;Ok(s) }fn main() {let str_file = read_text_from_file("hello.txt");match str_file {Ok(s) => println!("{}", s),Err(e) => {match e.kind() {// kind函数用于判断 Result 的 Err 类型io::ErrorKind::NotFound => {println!("No such file");},_ => {// 其他情况println!("Cannot read the file");}}}} }
泛型
- 函数的泛型
// 大小的比较 fn max<T>(array: &[T]) -> T {let mut max_index = 0;let mut i = 1;while i < array.len() {if array[i] > array[max_index] {max_index = i;}i += 1;}array[max_index] }
- 结构体与枚举类中的泛型
struct Point<T> {x: T,y: T } let p1 = Point {x: 1, y: 2}; let p2 = Point {x: 1.0, y: 2.0}; let p = Point {x: 1, y: 2.0};// error,推导类型不匹配
- 类方法的泛型
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()); }
- 特性(类似接口)
impl <特性名> for <所实现的类型名>
, 同一个类可以实现多个特性,但每个 impl 块只能实现一个。- 特性可以定义方法作为默认方法,因为是"默认",对象可以进行重定义
// 特性接口:实现类必须实现特性接口中的方法 trait Descriptive {fn describe(&self) -> String; } // 类属性 struct Person {name: String,age: u8 } // 类方法 impl Descriptive for Person {fn describe(&self) -> String {format!("{} {}", self.name, self.age)} }
- 函数做参数
- Rust中通过传递特性参数实现,例如回调函数等
- 任何实现了所传递特性的对象都可以作为这个函数的参数
// 基本使用 fn output(object: impl Descriptive) {println!("{}", object.describe()); } // 多类型 fn output_two<T: Descriptive>(arg1: T, arg2: T) {println!("{}", arg1.describe());println!("{}", arg2.describe()); }
集合与字符串
- 向量(Vector)是一个存放多值的单数据结构,该结构将相同类型的值线性的存放在内存中
- 向量的基本使用
let vector: Vec<i32> = Vec::new(); // 创建类型为 i32 的空向量 let vector = vec![1, 2, 4, 8]; // 通过数组创建向量 vector.push(16);// 在末尾追加单个元素
- 向量的拼接、
let mut v1: Vec<i32> = vec![1, 2, 4, 8]; let mut v2: Vec<i32> = vec![16, 32, 64]; v1.append(&mut v2);
- 字符串
- 中文是 UTF-8 编码的,每个字符长 3 字节
let string = String::new(); let one = 1.to_string(); // 整数到字符串 let float = 1.3.to_string(); // 浮点数到字符串 let slice = "slice".to_string(); // 字符串切片到字符串let mut hello = String::from("Hello");// 直接初始化成任何utf-8的字符串 s.push_str("oob"); // 追加字符串切片 s.push('!'); // 追加字符 // 拼接字符串 let s1 = String::from("Hello, "); let s2 = String::from("world!"); let s3 = s1 + &s2; // 用宏进行字符串改进 let s1 = String::from("tic"); let s2 = String::from("tac"); let s3 = String::from("toe"); let s = format!("{}-{}-{}", s1, s2, s3); // 计算长度 let s = "你好"; let len = s.len();// 6个字节长度 // 遍历字符串 let s = String::from("hello中文"); for c in s.chars() {println!("{}", c); }
- 散列表
let mut map = HashMap::new();
map.insert("color", "red");// 键值对的插入
map.insert("size", "10 m^2");
println!("{}", map.get("color").unwrap());// 值的获取
map.entry("color").or_insert("red");// 如果没有键为 "color" 的键值对就添加它并设定值为 "red",否则将跳过
类
- 基本用法
// second.rs // 类的属性 pub struct ClassName {field: i32, } // 类的方法 impl ClassName {pub fn new(value: i32) -> ClassName {ClassName {field: value}}pub fn public_method(&self) {println!("from public method");self.private_method();}fn private_method(&self) {println!("from private method");} } // main.rs mod second; use second::ClassName;fn main() {let object = ClassName::new(1024);// new一个对象object.public_method();// 对象调用方法 }
其他知识
内存模型
- Rust内核线程默认为8MB的栈大小,用户线程默认为2MB
方法
本质是面向对象机制下的函数
所有权
- 其他语言的内存管理
- C/C++ 需要手动的申请和释放内存资源,提高了开发效率,但是可能导致内存泄漏
- java手动申请资源,但由JVM自动释放
- 所有权规则
- Rust 中的每个值都有一个变量,称为其所有者。
- 一次只能有一个所有者。
- 值所有者的生命周期只在可用范围内,在生命范围结束时,编译器自动调用函数释放资源
- 变量范围
{// 在声明以前,变量 s 无效let s = "runoob";// 这里是变量 s 的可用范围 } // 变量范围已经结束,变量 s 无效
- 值传递类型
- 移动语义Move:
- 引用类型:栈中的指针变量指向堆内存,赋值时使用移动语义,赋值后原指针失效。
let s1 = String::from("hello"); let s2 = s1; // 赋值后s1失效,指向的堆内存所有权移动给s2
- 拷贝语义Clone:
- 基本数据类型:在栈中进行数据的深拷贝
let x = 5; let y = x; // 拷贝语义:赋值后均有效// 堆内存的拷贝 let s1 = String::from("hello"); let s2 = s1.clone();
- 移动语义Move:
- 函数参数所有权的传递
- 在栈中的基本数据类型的赋值是拷贝语义,在堆中的引用数据类型的赋值是移动语义
- 被当作函数返回值的变量所有权将会被移动出函数并返回到调用函数的地方,而不会直接被无效释放。
fn main() {let s = String::from("hello");// s 被声明有效takes_ownership(s);// s 的值被当作参数传入函数// 所以可以当作 s 已经被移动,从这里开始已经无效let x = 5;// x 被声明有效makes_copy(x);// x 的值被当作参数传入函数// 但 x 是基本类型,依然有效// 在这里依然可以使用 x 却不能使用 slet s2 = String::from("hello");let s3 = takes_and_gives_back(s2);// s2 被当作参数移动, s3 获得返回值所有权 } // 函数结束, 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 take_and_giveback(a_string:String)->String{a_string // a_string 被当作返回值移出函数 }
- 引用
- 引用是一个指向被引用对象的指针变量,是一种间接访问的方式
- 引用不会获得值的所有权,引用只能租借(Borrow)值的所有权。
- 如果被引用对象的所有权发生转移,需要重新租借
- 不能多重可变引用,为了防止写时碰撞
- Rust不允许返回局部变量的引用
// 基本引用 let s1 = String::from("hello"); let s2 = &s1; // 所有权转移引起的租借失效 let s1 = String::from("hello"); let mut s2 = &s1; let s3 = s1;// s1内存资源所有权转移到s3中,s2的租借失效 s2 = &s3; // 重新从 s3 租借所有权 // 可变租借 let mut s1 = String::from("run");// 被租借对象本身就是可变的 // s1 是可变的 let s2 = &mut s1;// 赋予租借者可变的权力 // s2 是可变的引用 let s3 = &mut s1;// error,不允许多重可变引用
移动语义move_semantics
- move语义
- 分配的堆内存对象在一个声明周期内只能有一个指针指向
- 重新的赋值或者传参一下,rust编译器都会认为变量所有权已经被交接出去,不能在使用该变量名称进行处理?
fn main() {let vec0 = Vec::new(); // 在堆上new一个vectorlet mut vec1 = fill_vec(vec0);// 调用函数println!("{} has length {} content `{:?}`", "vec1", vec1.len(), vec1);vec1.push(88); // 上面定义vec1必须是一个可变调用println!("{} has length {} content `{:?}`", "vec1", vec1.len(), vec1); }fn fill_vec(vec: Vec<i32>) -> Vec<i32> {let mut vec = vec;// vector添加数据元素vec.push(22); // 相当于Vec::push(&mut vec, 22),其中&mut vec为一个可变借用vec.push(44);vec.push(66);vec// 无分号表达式的返回 }
组织管理
- 基本概念
- 箱(Crate)是二进制程序文件或者库文件,存在于"包"中。
- 包(Package)
Cargo new
创建的就是一个工程包,一个包最多包含一个库"箱",可以包含任意数量的二进制"箱",但是至少包含一个"箱",src 目录下会生成一个 main.rs 源文件,Cargo 默认这个文件为二进制箱的根。 - 模块(Module)可以层层包含的树形结构
// 默认是私有的,只有在与其平级的位置或下级的位置才能访问,不能从其外部访问。
mod nation {pub mod government {fn govern() {}}mod congress {fn legislate() {}}mod court {fn judicial() {}}
}// 从crate开始的绝对路径
crate::nation::government::govern();、
// 从其中任一个开始的相对路径
nation::government::govern();
生命周期
- 生命周期注释
- 作用:描述引用的生命周期
'static
所表示的生命周期从程序运行开始到程序运行结束。
// 标注生命周期的一致性
fn longer<'a>(s1: &'a str, s2: &'a str) -> &'a str {if s2.len() > s1.len() {s2} else {s1}
}
fn main() {let r;{let s1 = "rust";let s2 = "ecmascript";r = longer(s1, s2);println!("{} is longer", r);}
}
文件与IO
- 程序命令行环境参数
- Rust 中主函数是个无参函数,环境参数需要开发者通过 std::env 模块取出
- Rust 标准库还没有提供直接从命令行读取数字或格式化数据的方法
fn main() {let args = std::env::args();println!("{:?}", args);
}
- 文件的创建和读写
- 打开的文件一定存放在可变的变量中才能使用 File 的方法
// 文件流的读取
use std::io::prelude::*;
use std::fs;fn main() {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);
}// 文件写入
use std::io::prelude::*;
use std::fs::File;fn main() {let mut file = File::create("D:\\\\text.txt").unwrap();file.write(b"FROM RUST PROGRAM").unwrap();
}
少年,我观你骨骼清奇,颖悟绝伦,必成人中龙凤。 不如点赞·收藏·关注一波
🚩点此跳转到首行↩︎
参考博客
- Rust中常量为什么用let不用const,变量用let mut不用var?
- 菜鸟教程