# Rust 语法学习 ## 用例 1:猜数游戏 生成一个随机 1-100 的数并让玩家猜测 ```rust // rust默认隐式引入prelude // 除了prelude以外都要显式引入 use rand::random; fn main() { // 使用常量定义一个偏置量 // 常量在编译时就已经确定,所以不能通过如CONST XX = XX()来初始化 // let声明的不可变变量虽然不可变,但是在编译时默认为未知的。 const BIAS: u8 = 1; println!("guess number game"); // let: 声明变量,默认不可变 // mut:声明变量可变 // String::new(),冒号表示这是一个String类的关联函数 // 关联函数就是类似于静态方法 let secret_number = random::() + BIAS; println!("secret number is {}", secret_number); // mut使得变量可变 let mut user_guess: u8 = 0; let mut user_input = String::new(); while user_guess != secret_number { // 清空之前的输入 user_input.clear(); // io::stdin():标准输入 // read_line:读取一行 // &:取地址符号,表示引用,默认不可变 // &mut:声明可变引用 std::io::stdin().read_line(&mut user_input).expect("read fail"); // 将输入的字符串转换为数字 // user_guess = user_input.trim().parse().expect("parse fail"); // 使用 match 表达式判断用户输入的是否为数字 // 比使用if else更简洁 user_guess = match user_input.trim().parse() { Ok(num) => num, Err(_) => { println!("please input a number"); continue; } }; // 判断用户输入的数字与随机数的大小关系 // if user_guess > secret_number { // println!("user number is too large"); // } else if user_guess < secret_number { // println!("user number is too small"); // } else { // println!("user number is correct"); // break; // } // 使用 match 表达式判断用户输入的数字与随机数的大小关系 // 比使用if else更简洁 match user_guess.cmp(&secret_number) { std::cmp::Ordering::Greater => println!("user number is too large"), std::cmp::Ordering::Less => println!("user number is too small"), std::cmp::Ordering::Equal => { println!("user number is correct"); break; } } println!("user number is {}", user_input); } } ``` ## 特性: shadow 在同一个作用域内,可以重复声明同名的变量,会覆盖之前的变量 ```rust fn main() { let x = 5; // 重复声明同名的变量,会覆盖之前的变量 let x = x + 1; let x = x * 2; println!("The value of x is: {}", x); // 12 } ``` ## 类型 ### 基本类型 #### 整数类型 - 整数:i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize - 整数类型范围: - 有符号:-2^n+1 ~ 2^(n-1)-1 - 无符号:0 ~ 2^n-1 - isize, usize:取决于运行程序的计算机的架构 - 表示 - hex: 0x12345678 - oct: 0o12345678 - bin: 0b1010_1010 - Byte: b'A' #### 浮点数类型 - 浮点数:f32, f64, 默认是 f64 - 表示 - 科学计数法:1.23e3 - 小数点:1.23 #### 布尔类型 - 布尔类型:bool - 表示 - true, false #### 字符类型 - 字符类型:char - 表示 - 'a', 'A', '1', '中' - 单引号表示 - 占用 4 字节 ### 复合类型 #### 元组类型(Tuple) - 可以将多个类型的多个值放在一个类型里 - 长度是固定的,一旦声明就无法改变 - 声明例: let tup: (i32, f64, u8) = (500, 6.4, 1); - 访问例 1: let (x, y, z) = tup; - 访问例 2: let x = tup.0; #### 数组类型(Array) - 可以将多个类型的多个值放在一个类型里 - 长度也是固定的,一旦声明就无法改变 - 想要使用有固定数量的元素时使用数组更有好处 - 声明例: let arr: [i32; 5] = [1, 2, 3, 4, 5]; - 声明例 2: let arr = [3; 5]; // 表示数组有 5 个 3 - 访问例: let x = arr[0]; - 没有 Vector 灵活 ## 函数 函数名小写,单词间使用下划线进行分割 例子: ```rust fn f(x: i32, y: i32) -> i32 { // 这里不加分号,因为是表达式,会返回值 // 函数默认使用最后一个表达式作为返回值 x + y } fn f2(x: i32, y: i32) { let z = { x + y + 1 }; z } ``` ## 控制流 ### if 表达式 - 如果使用多于一个的 if-else,最好使用 match 对代码进行重构 - if 表达式是一个表达式,所以可以将其赋值给一个变量 ```rust fn main() { let x = 5; if x < 5 { println!("x is less than 5"); } else if x > 5 { println!("x is greater than 5"); } else { println!("x is 5"); } // 使用 match 对代码进行重构 let x = 5; match x.cmp(&5) { std::cmp::Ordering::Less => println!("x is less than 5"), std::cmp::Ordering::Greater => println!("x is greater than 5"), std::cmp::Ordering::Equal => println!("x is 5"), } let y = if x < 5 { 1 } else { 2 }; } ``` ### 循环 ```rust fn main() { let mut x = 0; while x < 5 { println!("x is {}", x); x += 1; } x = 0; // 使用 .. 表示一个范围,不包含右边的数 // 实际的类型是std::ops::Range<{integer}> // 可以通过(1..5).rev() 表示一个倒序的范围 for x in 0..5 { println!("x is {}", x); } const A: [i32; 5] = [1, 2, 3, 4, 5]; // iter 方法返回一个迭代器,迭代器是一个可以遍历集合的对象 for x in A.iter() { println!("x is {}", x); } x = 0; loop { println!("x is {}", x); x += 1; if x > 5 { break; } } // loop 表达式是一个表达式,所以可以将其赋值给一个变量 let result = loop { if x > 5 { break x * 2; } x += 1; }; } ``` ## 所有权 - 内存管理分为两个部分:栈和堆 - 所有权是 Rust 最独特的特性之一 - 在 Rust 中,每个值都有一个变量,这个变量被称为所有者 - 每个值在任意时刻有且只有一个所有者 - 当所有者离开 scope(作用域),这个值将被删除 ### 作用域 例: ```rust fn main() { { let x = 5; println!("x is {}", x); } // x 已经离开作用域 } ``` ### copy trait - 赋值要么发生移动,要么发生复制 - Rust 中的基本类型都实现了 copy trait - 如果一个类型实现了 copy trait,则旧的变量在赋值后仍然可以使用 - 否则就像下面的 String 一样,赋值以后旧的变量会失效 - 内容全为已实现 copy 的 tuple 也是可 copy 的 ### 所有权和函数 - rust 函数传参与赋值操作类似 - 所以可能发生移动,使得一些变量失效 - 返回值时所有权也很发生移动或复制 - 一个变量的所有权总是遵循同样的模式 - 把一个值赋给其他变量的时候会发生移动 - 当一个包含 heap 数据的变量离开作用域时。这个值就会被 drop 函数删除 - 除非其所有权发生了转移 例: ```rust fn main() { let s = String::from("hello"); takes_ownership(s); // 这里会报错,因为s已经失效了 // println!("{}", s); let x = 5; makes_copy(x); // 这里不会报错,因为x仍然可以使用 println!("{}", x); let s1 = generates_string(); let s2 = String::from("hello"); let s3 = takes_and_gives_back(s2); println!("{}", s1); // 这里会报错,因为s2已经失效了 println!("{}", s2); println!("{}", s3); } fn takes_and_gives_back(a_string: String) -> String { a_string } fn generates_string() -> String { let s = String::from("hello"); s } fn makes_copy(x: i32) { println!("{}", x); } fn takes_ownership(some_string: String) { println!("{}", some_string); } ``` ## 引用和借用 - 引用是一个指向某个值的指针 - 引用不会获取到值的所有权 - 引用的变量默认是不可变的 - 引用的变量可以通过 mut 关键字声明为可变的 - 特定的作用域内,一个变量只能有一个可变引用 - 不可同时拥有可变引用与不可变引用 - 不可变引用可以有多个 - rust 在编译时会防止悬空指针的出现 例 ```rust fn main() { let s1 = String::from("hello"); // 这里的s2是一个引用,指向s1 let len = calculate_length(&s1); change_string(&mut s1); println!("{}", s2); } fn calculate_length(s: &String) -> usize { s.len() } fn change_string(s: &mut String) { s.push_str(", world"); } ``` ## slice 切片 - slice 是一个引用,指向一个字符串/数组的一部分 - 定义函数时使用字符串切片(&str)来代替字符串引用会使我们的 api 更加通用且不会损失任何功能 ```rust fn main() { let s = String::from("hello world"); let wordIndex = first_word(&s); let hello = &s[0..5]; let world = &s[6..11]; // 这里会报错,因为clear会导致可变,但是 s已被不可变引用 s.clear(); let a: [u8; 5] = [1, 2, 3, 4, 5]; let slice = &a[0..2]; } fn first_word(s: &str) -> usize { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return &s[i..]; } } &s[..] } ``` ## String - 相比于使用双引号创建的字符串,使用 String 类型可以动态地分配内存 - String 是一个可变的字符串类型 - String 是一个堆上的字符串,而不是栈上的字符串 - 底层实现上: - String 是一个结构体,包含一个指向堆上的字符串的指针和一个长度,一个容量 - 结构体储存在栈上 - 真实的字符串储存在堆上 ```rust fn main() { // 在heap上分配内存了 let mut s = String::from("hello"); // 和js类似,s1和s实际上是同一个字符串 // 但是因为一些所有权的问题,所以在s1生效以后,s就失效了 let s1 = s; // 克隆消耗资源,但是原来的不会失效了 let s2 = s1.clone(); // 这样会报错,因为s已经失效了 s.push_str(", world"); s1.push_str(", world"); println!("s is {}", s1); } ``` ## struct 结构体 - 结构体是一个自定义的类型 - 一旦结构体是可变的,那么结构体的所有字段就是可变的 - 只要 struct 是有效的,则里面的引用也都是有效的 ```rust struct User { name: String, age: u8, id: u32, } // tuple struct 元组结构体 struct Color(i32, i32, i32) // 这样可以将结构体打印出来 #[derive(Debug)] struct Rectangle { width: u32, height: u32, } // impl关键字用于为结构体实现接口或者关联函数或者方法 impl Rectangle { fn area(&self) -> u32 { self.width * self.height } fn can_hold(&self, other: &Rectangle) -> bool { self.width > other.width && self.height > other.height } // 关联函数,不需要实例化就可以调用,通常用于创建结构体 // 类似于静态方法 fn square(size: u32) -> Rectangle { Rectangle { width: size, height: size, } } } fn main() { let id: u32 = 1; let user = User { name: String::from("hello"), age: 18, id, // 如果字段名和变量名相同,可以省略变量名 }; let user2 = User { name: String::from("world"), id: 2, ..user //struct更新语法 }; println!("user is {}", user.name); let color = Color(255, 0, 0); let rect = Rectangle { width: 10, height: 20, }; let rect2 = Rectangle { width: 5, height: 10, }; println!("rect is {:#?}", rect); println!("rect area is {}", rect.area()); println!("rect can hold rect2? {}", rect.can_hold(&rect2)); } ``` ## 枚举 - 枚举是一个自定义的类型 例 ```rust enum IpAddr { V4(u8, u8, u8, u8), V6(String), } fn main() { let home = IpAddr::V4(127, 0, 0, 1); let loopback = IpAddr::V6((String::from("::1"))); } ``` ## Option - Option 是一个枚举类型,表示一个值可能不存在 - 包含在预导入库中,可以直接使用 ```rust enum Option { Some(T), None, } ``` - 如 let x: Option = None; - 通过unwrap方法可以获取到Some的值 ## match - match可以提供一种匹配 ```rust enum UsState { Alabama, Alaska, } enum Coin { Penny, Nickel, Dime, Quarter(UsState), } fn value_in_cents(coin: Coin) -> u8 { match coin { Coin::Penny => 1, Coin::Nickel => 5, Coin::Dime => 10, Coin::Quarter(state) => { println!("State quarter from {:?}!", state); 25 }, } } fn main() { let coin = Coin::Quarter(UsState::Alaska); println!("value_in_cents is {}", value_in_cents(coin)); } ```