本文主要是介绍rust 助剂,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
https://kaisery.github.io/trpl-zh-cn/foreword.html
rust 的核心思想是 由程序员,语法,编译器 共同 维护 程序内的变量生成,使用,复制,转移和销毁。
基本数据类型
i8,i16,i32,i64,i128 // 有符号整数
u8,u16,u32,u64,u128 // 无符号整数
isize, usize // 机器字长整数 和 无符号整数,64 位就是 64 位长
f32,f64 // 浮点数,money 估计也要用到此值
char // 字符类型
bool:true,false let bx: bool = true; let mut by: bool = false; //bool 类型
String let str1: String = String::from ("sss"); let str2: str = "sss2"; // 要引用 std::string::String // 字符串类型
silce // 切片,这是个片段引用
复合类型(Compound types)可以将多个值组合成一个类型。Rust 有两个原生的复合类型: 元组(tuple)和数组(array)。
type Name = String; // 类型别名,只是个别名不是新类型
下面是字面值(字面量会被编译到程序中)
Decimal 98_222;
Hex 0xff;
Octal 0o77;
Binary 0b1111_0000;
Byte (u8 only) b'A';
类型转换
as 转换符 :
‘c’ as u8
23 as char
i64 as i128; i32 as i64
常量变量
const MAX_POINTS: u32 = 100_000; // 静态
let x = 5;// 绑定变量
let x:u32 = 5;
let mut x = 5;// 声明可变绑定
let mut x:u64 = 5;
let an_integer = 5i32; // 通过数据来定义
let y = 3.0_f64; // 后缀指定类型
let _noisy_unused_variable = 2u32; // 如果某个值绑定了未使用,会报编译警告。只要加个前下划线就不会编译警告了.
let spaces = " ";// 字面值
let spaces = spaces.len ();// 这是重定义 space,而不是设置
let mut spaces = " ";
spaces = ‘3’;// 这是变更值内容
let guess: u32 = "42".parse ().expect ("Not a number!");// 这个是转换 + 验证抛错
运算符
+-*/% 5 大运算,加减乘除余 整数除 取整,% 求余数,浮点数除就是浮点结果
+=, -=, *=, /= 加等,减等,乘等,除等
其他参考这里: https://kaisery.github.io/trpl-zh-cn/appendix-02-operators.html
组合结构
let x: (i32, f64, u8) = (500, 6.4, 1); // 元组
let x1 = (500i32, "500", 500.0f64); // 元组带名字赋值
x1.0 // 访问 500i32,x1.1 访问 "500" x1.2 访问 500.0f64
let (x, y, z) = x1; // 拆分赋值, 解构(destructuring)赋值
let a = [1, 2, 3, 4, 5]; // 数组
let mut a:[i32;5]=[1,2,3,4,5]; // 可变数组绑定 &a [0]=2 后 [2,2,3,4,5]
let ys: [i32; 500] = [0; 500]; //i32 类型 500 长度,用 0 初始化 500 长度?
// 数组的定义写法 变量名:[类型;数组长度]
// 简化 (1..4) 1-4 组合 =[1,2,3,4] 但不一定是数组,也可能是 vector
打印 print 格式化打印
5 个打印相关宏 :format!、format_arg!、print!、println!、write!
两个 trait: Debug、Display。
print!、println!、就是将 format! 的结果输出到控制台;
println!("{} days", 31); // 和 format!("hello {}", "world!"); 语法类似,因为集成了 format!
println!("{0}, this is {1}. {1}, this is {0}", "Alice", "Bob"); // 通过标号访问插值参数
println!("{subject} {verb} {object}",object="the lazy dog",subject="the quick brown fox",verb="jumps over"); // 通过名字访问插值
println!("{} of {:b} people know binary, the other half doesn't", 1, 2); // 插值时格式化打印
println!("{number:>width$}", number=1, width=6); // 打印 1, 前面控制宽度 6
println!("{number:>0width$}", number=1, width=6); // 打印 1,前面用 0 填充宽度 6
println!("My name is {0}, {1} {0}", "Bond"); // 没有 {1} 对应值,必须加入才能编译通过
#[allow(dead_code)]
struct Structure (i32);// 这个是元组类型结构,用.0 .1 访问成员
fmt::Debug: Uses the {:?} marker. // 实现了 Debug 特性可用 {:?} 标记打印信息
fmt::Display: Uses the {} marker. // 实现了 display 特性可用 {} 打印信息
println!("This struct`{}`won't print...", Structure (3)); // 注意打印占位符 {} 不是 {:?},需要 Structure 实现 Display trait
println!("{1:?} {0:?} is the {actor:?} name.", "Slater", "Christian", actor="actor's");// {1:?} 打印第二个 数组标记法 // 打印名字标记法
println!("{:#?}", peter); // 漂亮打印:其实就是参数分行打印,一个参数一行,易读
impl Display for Structure { // 这个实现了 Display
fn fmt (&self, f: &mut fmt::Formatter) -> fmt::Result { // 这行就是 Display trait 的具体方法描述
write!(f, "--({})--", self.0) 这里是实现:一定不能直接用 self,必须用 self.0 不然会导致嵌套调用 “overflowed its stack”
}
}
format 宏语法
format!("test");
format!("hello {}", "world!");
let xs: String = String::from("big joy");
format!("x = {}, y = {y}, z_str={z}", 10, y = 30, z = xs);
函数 - 方法
fn function_name () { ...} // 孤立的叫 函数,
(impl 可理解为对象,类似 class 关键字) impl 的 函数 且首个参数 是 & self 的 叫 方法(类似对象方法),impl 内首个参数非 & self 的方法叫 关联函数(类似这个对象的静态方法)
impl AveragedCollection { /* 具体参见 rust 的面向对象部分,这里给出范例主要为理解 impl 是什么 */
pub fn add( &mut self, value: i32) { self.list.push(value); }
pub fn average( &self) -> f64 {self.average/* 这是表达式,会返回值和 return 类似。没分号哦!*/}
pub fn func1 (i:String) -> String { "...." } // 这个就是静态方法了
}
fn another_function (x: i32, y: i32)->i32// 多参数单返回值
fn plus_one (x: i32, y: i32) -> (i32,i32)// 多参数多返回值
包含语句和表达式的函数体:语句(Statements)是执行一些操作但不返回值的指令 (有分号结尾)。表达式(Expressions)计算并产生一个值(无分号结尾)。
函数调用是一个表达式。宏调用是一个表达式。
控制结构
条件表达式不带括号
if number < 5 { // 单个 if else
} else {
}
if number % 4 == 0 { // 一堆 if else if
} else if number % 3 == 0 {
} else if number % 2 == 0 {
} else {
}
let number = if condition { //if 赋值,有分号的叫语句,没分号的叫表达式 - 含有返回
5 // 这里是表达式。会返回
} else {
6 // 这里是表达式。会返回
};
循环
Rust 有三种循环:loop、while 和 for。
loop { // 玩命的循环
if true {break;} // 还是能退出的 停止循环的 break 表达式
}
let result = loop { // 这个通过 loop 赋值 最后一个值反出来赋值给 result, 很异类用法
counter += 1;
if counter == 10 {
break counter * 2;// 停止循环的 break 表达式
}
};
while number != 0 { // 不玩命的循环,当条件为真,执行循环。
}
for element in a .iter() { // 挨着循环
}
for number in (1..4) .rev() { // 倒着循环
}
let v = vec![1;10];
for (index, e) in v.iter ().enumerate () { // 带索引循环
}
for (pos, e) in v.iter()
函数参数 - 引用参数
fn calculate_length(s: &String) -> usize // 引用参数,所有权来说就是借用
let len = calculate_length(&s1);
// 可变引用函数
fn change(some_string: &mut String) { // 所有权 可变借用
let mut s = String::from("hello");
change( &mut s);
slice 切片
let s = String::from ("hello world"); // 这里是初始化一个字符串,详细请看 String
字符串字面值就是 slice . &str 就是 slice,&str 可作为方法参数
&s [起点索引值.. 终点索引值] start..end 语法代表一个以 start 开头并一直持续到但不包含 end 的 range。
let hello = &s [0..5];// 注意 字符串 时 这里取的是字节, utf-8 不能这样取!必死!
let world = &s [6..11];// 数组的话 ok
let hello = &s[0..=4];// 如果需要包含 end,可以使用 ..= 不用等号就是不含 4 号,加等号含有 4 号
let slice = &s [0..2]; // 开头开始取
let slice = &s [..2]; // 开头开始取,省了 0
let len = s.len();
let slice = &s [3..len]; //3 到结尾
let slice = &s [3..]; //3 到结尾
let slice = &s [0..len]; // 全取
let slice = &s [..]; // 全取
fn first_word(s: &String) -> &str {
&s [..] // 表达式,返回 slice 形式:&str
}
fn first_word (s: &String) -> &str {// 这里(不可变)借用 s,返回 slice 引用,有借鉴 let bytes = s.as_bytes (); for (i, &item) in bytes.iter ().enumerate () { if item == b' ' { return &s [0..i]; } } &s [..]// 用表达式返回,有借鉴。某值的不可变引用时,就不能再获取一个可变引用。 }
let s = "Hello, world!";
这里 s 的类型是 &str:它是一个指向二进制程序特定位置的 slice。这也就是为什么字符串字面值是不可变的; &str 是一个不可变引用。
字符串接入,字符串 + 操作
let mut s = String::from ("lo"); // 初始化一个字符串 可修改的 s.push ('l');//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 = s1 + "-" + &s2 + "-" + &s3; // 行但笨 let s = format!("{}-{}-{}", s1, s2, s3); // 女子 // 异类字符 slice let hello = "Здравствуйте"; let s = &hello [0..4];// 必须双数,取得字节(这里要注意 utf8)! for c in "Здравствуйте".chars () { // 取得字面字符 println!("{}", c); } for b in "Здравствуйте".bytes () { // 取得字节 println!("{}", b); }
字符串相等性对比:
str::eq (str1, str2) 对比两者必须是 String 或者 str 类型
不排除还有其他对比方式
引用和借用(rust 的重点,精髓,关键)
栈(Stack)与堆(Heap) 和此有一定关系注意数据参数存放位置, 作用域(scope)是一个重要概念
let s = "hello";/* 存在栈上 */let s = String::from ("hello");/* 存在堆上 */
栈上值复制,无所有权,堆上值引用,有所有权;
let x = 5;let y = x; /* 都在栈上,这之后 x,y 均可使用 */
let s1 = String::from ("hello"); let s2 = s1; /* 都在堆上,这之后 s1 无法使用,所有权转移到 s2 上 */ 除非:let s2=s1.clone (); 都可用
Rust 不允许自身或其任何部分实现了 Drop trait 的类型使用 Copy trait。
如下类型自动 Copy:所有整数类型 i64;布尔类型 bool;所有浮点数类型 f64;字符类型 char;元组,当且仅当其包含的类型也都是 Copy 的时候。比如,(i32, i32) 是 Copy 的,但 (i32, String) 就不是。
函数非引用参数会带走所有权,返回非引用,会带出所有权。
&s1 语法允许我们创建一个 指向 值 s1 的引用,但是并不拥有它。获取引用作为函数参数称为 借用(borrowing)
fn calculate_length( s: &String) -> usize { // 借用 将获取引用作为函数参数称为 借用(borrowing)
尝试修改借用的变量,(默认)不允许修改引用的值。除了一些特别的情况,比如标准库基本类型字符类型 char.encode_utf8 () 方法。
可变借用:fn change(some_string: &mut String) let mut s = String::from("hello"); change(&mut s);
在特定作用域中的特定数据有且只有一个可变引用。
可变借用一个样例:
pub fn unicode_utf8 (ustr: &String) -> Vec<u8> {// 借用来的参数 ustr let mut utf8_str: Vec<u8> = Vec::new (); for c in ustr.chars () {// 循环每个字符 let len = c.len_utf8 ();// 获取其 utf8 长度 let mut cb:[u8; 4] = [0u8; 4];// 可变的引用,取 0-4 个字节 {// 这里构成一个作用域,用于隔离另一个(后面一个)引用借用 let cb_a = &mut cb [..len];// 从 cb 中创建一个可修改引用借用, let r = c.encode_utf8 (cb_a);// 将可修改引用借用给 encode_utf8 函数,内部有不安全代码 println!("r = {:?}", r); }// 作用域后 cb_a 已经失效了,这个引用借用被释放了 let cb_b = &cb [..len];// 再来个引用借用,但是这里不需要修改了。 for b in cb_b { utf8_str.push (b.clone ());// 循环将 encode_utf8 修改的内容装到 vec 中。 } } return utf8_str;// 返回将 unicode 转换 utf8 编码后的 byte 数组 vec }
多个不可变引用是没有问题。不能在拥有不可变引用的 同时拥有可变引用。
在任意给定时间,只能 拥有如下中的一个:
一个可变引用。
任意数量的不可变引用。
引用必须总是有效的。
所有权要点(rust 的重点,精髓,关键) ★★★★★
栈上数据 拷贝,有两个值;
堆上数据,使用所有权 移动(move),而不是浅拷贝。
let s1 = String::from("hello");
let s2 = s1;// 解读为 s1 被 移动 到了 s2 中,这之后 s1 无法再使用,所有权转移到 s2 上了
let s1 = String::from("hello");
let s2 = s1.clone ();// 这里深拷贝了 s1 的数据。所以 s1 和 s2 都可以使用,出现两个所有权者
fn takes_ownership (some_string: String) // 这种方法定义表示输入参数所有权转移到函数内部了
fn takes_ownership (some_string: &String) // 这种方法定义表示输入参数所有权被借用进来了
对于基本栈类型 fn makes_copy (some_integer: i32)// 这里参数是拷贝进来的,另外建立了所有权
fn gives_ownership (input: String) -> String { // 这里接收一个所有权,再返回数据将函数内部所有权转移到外部
let (s2, len) = calculate_length (s1);// 此方法传入 s1 所有权,处理后返回所有权到 s2,还有其他返回 (len)。元组返回处理所有权。但是有些罗嗦
引用(references)借用 & 符号就是 引用,它们允许你使用值但不获取其所有权。
fn calculate_length (s: &String) -> usize {// 输入参数借用外部数据,但是不获取其所有权。获取引用作为函数参数称为 借用(borrowing)(不允许修改引用 / 借用 )
与使用 & 引用相反的操作是 解引用(dereferencing),它使用解引用运算符,*。
fn change (some_string: &mut String) {...// 可变借用
let mut s = String::from ("hello");// 定义可变绑定
change (&mut s);// 可变借用到函数内部( 一次只有一个可变借用(可以使用大括号隔离多个可变借用 ))
{let r1 = &mut s;} //r1 在这里离开了作用域,所以我们完全可以创建一个新的引用 let r2 = &mut s;
也 不能在拥有不可变引用的同时拥有可变引用。
悬垂引用(Dangling References)在函数内部创建,返回其指针,但是函数结束其所有者会被释放
fn dangle () -> &String { //dangle 返回一个字符串的引用 离开作用域并被丢弃。其内存被释放。危险!
解决方法是直接返回 String。fn no_dangle () -> String {...
总结常用方法:
String 转移 (输入)
&String 借用(输入)
解决悬垂引用错误的方法是 返回 所有权(不是返回地址) -> String 所有权转移出来(函数输出)
slice 不可变借用
作用域隔离 {}
所有权几个要点
- Rust 中的每一个值都有一个被称为其 所有者(owner)的变量。
- 值有且只有一个所有者。
- 当所有者(变量)离开作用域,这个值将被丢弃。
struct 结构体
struct User { // 定义
username: String,
sign_in_count: u64,
active: bool,
}
let user1 = User { // 初始化赋值,类似 json 的结构
username: String::from ("someusername123"),// 后面省略
};
let mut user1 = User { // 实例一个可变的结构体变量
username: String::from ("someusername123"),// 后面省略
};// 实例可变,字段可变,实例不可变,字段就不可变。
user1.username = String::from ("another"); // 赋值
变量与字段同名时的字段初始化简写语法(结构体语法糖)
有 字段 let username
User {
username,// 这个就是直接 字段名和属性名一样直接简化赋值
active: true,
sign_in_count: 1,
}
let user2 = User {
username: String::from("anotherusername567"),
active: user1.active,
sign_in_count: user1.sign_in_count,
};
可简化为
let user2 = User {
username: String::from("anotherusername567"),
..user1
};
结构体数据的所有权
首先 username: &str, 这样的写法会被编译器否认
需要生命周期辅助
结构 - 方法语法
#[derive (Debug)] // 这个让结构体可被打印出来
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle { // 方法语法,在 impl 中叫方法,孤立的叫函数
fn area (&self) -> u32 { // 注意 self 和 py 类似
self.width * self.height
}
}
let rect1 = Rectangle { width: 30, height: 50 };
rect1.area () // 用变量。成员调用
关联函数(associated functions)
不加 & self 就是关联函数,类似静态函数。加 & self 类似对象函数 (只在 impl(impl 可理解为对象)中)
impl Rectangle {
fn square(size: u32) -> Rectangle {
Rectangle { width: size, height: size }
}
}
Rectangle::square (100) // 关联函数访问
枚举,每个值可不同类型
enum Message {
Quit,
Move {x: i32, y: i32}, // 匿名结构体
Write(String),
ChangeColor (i32, i32, i32), // 元组
}
Message::ChangeColor // 访问方法
enum Option<T> { // 官方一个结构用于返回错误和正常结果
Some(T),
None,
}
#[derive(Debug)] // So we can inspect the state in a minute
enum UsState {
Alabama,
// ... etc
}
enum Coin {
Dime,
Quarter (UsState),// 这个枚举类型是另一个枚举
}
match coin { //match 语法
Coin::Dime => 10, // 直接返回(表达式)
Coin::Quarter (state) => { // 含有函数体
println!("State quarter from {:?}!", state);
25
},
//_ => (), // 通配符匹配,匹配所有剩余项目
}
// 只匹配一个其他忽略
let some_u8_value = Some(0u8);
match some_u8_value {
Some(3) => println!("three"),
_ => (),
}
// 只匹配一个其他忽略 简化写法
if let Some(3) = some_u8_value {
println!("three");
} else { ... }
// 这是一组写法,值得学习
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None, // 空返回
Some (i) => Some (i + 1), // 有结果返回
}
}
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
pub
pub // 控制可见 在 fn impl mod 前面
模块引用(1.31 后有变化)
模块目录 mymod/lib.rs 中注册同级别模块(文件),lib.rs 文件中(pub mod xmod;)==(指向)同目录 mod 名(xmod.rs)
extern crate my_library; // 先进入模块名 同下文的 a
a::series::of::nested_modules (); // 完整路径模块引用样例,类似静态方法
use a::series::of;//use 样例,到 of 这个级别。之后就可以通过 of 引用此结构以下的内容
of::nested_modules (); //use 简短 引用路径
use a::series::of::nested_modules;// 这是到最后一个级别,直接 use 到此函数
nested_modules (); // 最深到函数
use TrafficLight::{Red, Yellow}; // 引入多个
let red = Red;
let yellow = Yellow;
let green = TrafficLight::Green;// 也可以直接全路径引用,只是还是要完整路径引入
// * 语法,这称为 glob 运算符(glob operator)
use TrafficLight::*; // 全引入
::client::connect (); // 开头双冒表从根模块引用开始
super::client::connect (); //super 表上一级模块开始引用,是上一级不是根!
#[cfg(test)]
mod tests {
use super::client; // 在 mod 内部 super 上一级 引入,少些好多 super
#[test]
fn it_works() {
client::connect();
}
}
use mymod::mymod2::mymod3 as mm3; //use as 语法 use 别名 我就说肯定有类似的!
注意:默认好象是不引用 std 的,有用到 std 的地方需要在开始 use std;
集合 vcetor
默认引入,无需 use
let v: Vec<i32> = Vec::new (); // 指定 类型,也可推导类型
let v = vec![1, 2, 3]; // 用宏 创建的。 类型是推导的
let mut v = vec![1, 2, 3]; // 定义可变集合
v.push (5); //push 进去,末尾添加方式(此刻可能发生内存位置变更,不能在 push 前引用:类似此语句 let first = &v[0];)
let third: &i32 = &v [2]; // 访问方法: 用索引访问,超界访问会导致崩溃
let third: Option<&i32> = v.get (2); //get 加索引访问,注意返回类型 超界处理良好
match v.get (2) { // 这是个范例 通过 get 获取了,处理了 None 的情况 Some (third) => println!("The third element is {}", third), None => println!("There is no third element."), }
let v = vec![100, 32, 57]; // 遍历 1
for i in &v {
println!("{}", i);
}
let mut v = vec![100, 32, 57]; // 可变更遍历
for i in &mut v {
*i += 50; // 使用 += 运算符之前必须使用解引用运算符( *)获取 i 中的值。
}
// 枚举 的 vector 感觉好复杂哦!!!用 vector 结合 枚举来 在 列表中 存储不同类型数据,感觉很独特。
enum SpreadsheetCell { Int(i32), Float(f64), Text(String), } let row = vec![ SpreadsheetCell::Int(3), SpreadsheetCell::Text(String::from("blue")), SpreadsheetCell::Float(10.12), ];
字符串
核心一种: str , 被借用的形式: &str, 字符串 slice, 标准库: String
let mut s = String::new (); // 初始化 空
let data = "initial contents"; // 字面值
let s = data.to_string (); // 字面值 转换 String ,to_string 方法从字符串字面值创建 String
let s = "initial contents".to_string ();// 该方法也可直接用于字符串字面值:
let s = String::from("initial contents"); //
使用 + 运算符或 format! 宏来拼接 String 值。
let mut s = String::from("foo");
s.push_str("bar"); // push_str 方法向 String 附加字符串 slice
//---
let mut s1 = String::from ("foo"); // 定义可变
let s2 = "bar"; // 定义另一个
s1.push_str (s2); //push 到 s1 中,转成 字符串 切片 slice
println!("s2 is {}", s2); // 打印 s2,s2 这里是切片,所以可以运行
s.push ('l'); //push 只 接入 字符
// ----
let s1 = String::from("Hello, "); let s2 = String::from("world!"); let s3 = s1 + &s2; // 注意 s1 被移动了,不能继续使用
// + 运算符将两个 String 值合并到一个新的 String 值中
// 只能将 &str 和 String 相加,不能将两个 String 值相加。
// &String 可以被 强转(
coerced)成 &str 解引用强制多态(deref coercion)
// 把 &s2 变成了 &s2[..] // 所以 s2 在这个操作后仍然是有效的 String
// 语句会获取 s1 的所有权,附加上从 s2 中拷贝的内容,并返回结果的所有权
// 对于更为复杂的字符串链接,可以使用 format! 宏
let s1 = String::from("tic");let s2 = String::from("tac");let s3 = String::from("toe");
let s = format!("{}-{}-{}", s1, s2, s3);
// 不会获取任何参数的所有权。
String 是一个 Vec<u8> 的封装。
let hello = "Здравствуйте";
let s = &hello [0..4]; //s 是 Зд
// &hello [0..1] 会发生崩溃,原因是 这只能取到字节,不能取到完整字符。应该小心谨慎的使用这个操作
for c in "नमस्ते".chars () { // 这才是 保险的 循环字符方法。 println!("{}", c); }
for b in "नमस्ते".bytes () { // 这 可以 循环出 所有字节,在字节层面 是理想的方法 println!("{}", b); }
HashMap
use std::collections::HashMap; // 要 use
let mut scores = HashMap::new (); // 哈希 map 将它们的数据储存在堆上
scores.insert (String::from ("Blue"), 10); // 此处推导了 map 的键值对类型
scores.insert(String::from("Yellow"), 50);
// 这是个杂交组合法,要 use std::collections::HashMap;
let teams = vec![String::from ("Blue"), String::from ("Yellow")]; // 定义键队列
let initial_scores = vec![10, 50]; // 定义值队列
let scores: HashMap<_, _> = teams.iter(). zip(initial_scores.iter ()).collect (); // 这里杂交组合两个 vector,键值对类型用占位符 然后推导出来
// 插入值 所有权转移 String 这样拥有所有权的值,其值将被移动而哈希 map 会成为这些值的所有者
let field_name = String::from("Favorite color");
let field_value = String::from("Blue");
let mut map = HashMap::new();
map.insert (field_name, field_value); // 这里插入后两个值所有权被转移到 map 内部了 这里 field_name 和 field_value 不再有效,
访问 map
let score = scores.get (&team_name); // 返回 Option/Some/None 结构
for (key, value) in &scores { // 遍历
println!("{}: {}", key, value);
}
scores.insert (String::from ("Blue"), 25); // Blue 已经存在的话 insert 是 覆盖
scores.entry (String::from ("Blue")).or_insert (50); // 没有就插入,有跳过
// 找到一个键对应的值并根据旧的值更新它
let text = "hello world wonderful world";
let mut map = HashMap::new();
for word in text.split_whitespace() {
let count = map.entry (word).or_insert (0); //or_insert 方法事实上会返回这个键的值的一个可变引用(&mut V)。
*count += 1; // 为了赋值必须首先使用星号(*)解引用 count
}
异常 抛错
当出现 panic 时,程序默认会开始 展开(
unwinding)
[profile] //cargo.toml 配置 panic 不展开而是直接 abort
panic = 'abort'
[profile.release] // 这是发布模式下,不展开直接退出 abort
panic = 'abort'
panic!("crash and burn");// 抛错语句
设置 RUST_BACKTRACE 环境变量来得到一个 backtrace backtrace 是一个执行到目前位置所有被调用的函数的列表。
RUST_BACKTRACE=1 cargo run // 在运行 cargo run 前面设置 RUST_BACKTRACE=1,设置当前编译环境变量
enum Result<T, E> { // 可恢复的错误 的 枚举
Ok(T),
Err(E),
}
let f = File::open("hello.txt");
let f = match f { // 用 match 处理成功和失败
Ok(file) => file,
Err(error) => {
panic!("There was a problem opening the file: {:?}", error)
},
};
// 一个错误处理范例 这个范例嵌套了 match
use std::fs::File; use std::io::ErrorKind; fn main () { let f = File::open ("hello.txt"); let f = match f { Ok (file) => file, Err (ref error) if error.kind () == ErrorKind::NotFound => { match File::create ("hello.txt") { Ok (fc) => fc, // 成功的处理 Err (e) => { panic!( "Tried to create file but there was a problem: {:?}", e // 表达式返回了 e ) }, } }, Err (error) => { panic!( "There was a problem opening the file: {:?}", error ) }, }; }
let f = File::open ("hello.txt").unwrap (); //unwrap 帮调用 panic! 宏 但是无法体现具体错误
let f = File::open ("hello.txt").expect ("Failed to open hello.txt"); //expect 可以体现不同的错误,更好
传播错误
fn read_username_from_file () -> Result<String, io::Error>{ // 返回参数是 “标准库结果” let f = File::open ("hello.txt"); let mut f = match f { Ok (file) => file, Err (e) => return Err (e), // 返回错误 }; let mut s = String::new (); match f.read_to_string (&mut s) { Ok (_) => Ok (s), Err (e) => Err (e), } }
简化的错误传播
fn read_username_from_file () -> Result<String, io::Error> { // 注意?关键 let mut f = File::open ("hello.txt")?; let mut s = String::new (); f.read_to_string (&mut s)?; Ok (s) } let mut s = String::new (); File::open ("hello.txt")?.read_to_string (&mut s)?; // 链式方法调用 Ok (s)
? 只能被用于返回值类型为 Result 的函数
泛型
struct Point<T> { // 单泛型 x: T, y: T, }
struct Point<T, U> { // 多泛型 x: T, y: U, }
enum Option<T> { // 单泛型枚举 Some (T), None, }
enum Result<T, E> { // 多枚举泛型 Ok (T), Err (E), }
impl<T> Point<T> { // 泛型方法定义 在 impl 之后声明泛型 T ,这样 Rust 就知道 Point 的尖括号中的类型是泛型而不是具体类型。 fn x (&self) -> &T { &self.x } }
impl Point<f32> { // 特定类型方法定义 fn distance_from_origin (&self) -> f32 { (self.x.powi (2) + self.y.powi (2)).sqrt () } }
impl<T, U> Point<T, U> { // 双泛型 方法定义,方法中还有泛型 fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> { Point { x: self.x, y: other.y, } } }
trait 类似接口 定义共享行为
pub trait Summarizable {// 定义 trait fn summary (&self) -> String; }
impl Summarizable for NewsArticle {// 为类型 NewsArticle 实现这个 trait fn summary (&self) -> String { format!("{}, by {} ({})", self.headline, self.author, self.location) } }
impl Summarizable for Tweet {// 为类型 Tweet 实现 Summarizable trait fn summary (&self) -> String { format!("{}: {}", self.username, self.content) } }
限制是:只能在 trait 或对应类型位于我们 crate 本地的时候为其实现 trait。换句话说,不允许对外部类型实现外部 trait。例如,不能在 Vec 上实现 Display trait,因为 Display 和 Vec 都定义于标准库中。
pub trait Summarizable {// 默认实现 fn summary (&self) -> String { String::from ("(Read more...)") } }
impl Summarizable for NewsArticle {} // 空 impl 块,直接使用默认 trait 行为 pub trait Summarizable { // 默认实现可以调用未实现的 方法 fn author_summary (&self) -> String; // 这个行为 由 实现 trait 者去定义 fn summary (&self) -> String { format!("(Read more from {}...)", self.author_summary ()) // 调用 author_summary,然后再调用特定行为 } }
Trait Bounds 应该就是约束,实现约束,泛型实现 trait 约束
pub fn notify<T: Summarizable>(item: T) { println!("Breaking news! {}", item.summary()); }
fn some_function<T: Display + Clone, U: Clone + Debug>(t: T, u: U) -> i32 { // 多个约束 fn some_function<T, U>(t: T, u: U) -> i32 //where 方式多个约束 where T: Display + Clone, U: Clone + Debug { ... }
trait 作为参数,指定了 impl 关键字和 trait 名称,及参数 item 可以是实现了 Summary 的任意类型
pub fn notify(item: impl Summary) { println!("Breaking news! {}", item.summarize()); }
生命周期注解语法
生命周期注解并不改变任何引用的生命周期的长短。只是个注解
&i32 // a reference
&'a i32 // a reference with an explicit lifetime
&'a mut i32 // a mutable reference with an explicit lifetime
它只是个变量生命周期的标注,通过标注来识别生命周期范围,实际不影响变量生命周期
let s: &'static str = "I have a static lifetime."; // 静态生命周期,全程序周期,字符串字面值自带
“ 生命周期也是泛型” - 这句话让我直接炸裂!我艸,我说呢!写在 <> 内!
问题:泛型的生命周期呢?是不是要直接定义在泛型类型定义中?
多个生命周期定义只能通过泛型类型,或者结构体类型来定义?
生命周期也存在约束(where)?
fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str { // 这里实际上是永远都不能编译的?除非用方法语法?
测试
cargo test // 项目目录内运行此命令 就提取测试来运行了
#[cfg (test)] // 测试模块 属性注解。属性(attribute)是关于 Rust 代码片段的元数据
mod tests {
use super::*; // 引用要测试的模块 为哈是 super ,要测试的模块同级别
#[test] // 测试方法 属性注解
fn it_works() {
assert_eq!(2 + 2, 4); // 测试 断言宏
}
}
assert! 宏由标准库提供,参数 bool,false 时 assert! 调用 panic! 宏 抛错
assert_eq! 相等断言宏 和 assert_ne! 不相等断言宏
assert_eq! 和 assert_ne! 宏在底层分别使用了 == 和!=。被比较的值必需实现了 PartialEq 和 Debug trait。
可以直接在结构体或枚举上添加 #[derive (PartialEq, Debug)] 注解。
let result = greeting("Carol");
assert!(result.contains ("Carol")); // 断言结果中含有 “Carol”
assert!(
result.contains("Carol"),
"Greeting did not contain name, value was`{}`", result // 这里类似重载函数,打印出明确信息
);
#[test]
#[should_panic] // 这里表示 “应该抛错” 才是对的
#[should_panic (expected = "Guess value must be less than or equal to 100")] // 出错信息包含 expected 的字符串就通过
#[ignore] // 忽略此测试
fn greater_than_100() {
Guess::new(200);
}
运行测试
cargo test -- --test-threads=1 // 单线程运行测试
cargo test -- --nocapture // 测试会输出函数中打印的内容,就是说不截获(并丢弃)屏幕输出
cargo test one_hundred // 运行 含有 “one_hundred” 的测试
cargo test -- --ignored // 只运行忽略的测试
Rust 社区倾向于根据测试的两个主要分类来考虑问题:单元测试(unit tests)与 集成测试(integration tests)
测试模块的 #[cfg (test)] 注解告诉 Rust 只在执行 cargo test 时才编译和运行测试代码,而在运行 cargo build 时不这么做。
非 pub 函数可以测试
集成测试
目录中创建 tests 目录下放测试代码文件。
extern crate adder; // 需要导入指定模块
#[test]
fn it_adds_two() {
assert_eq!(4, adder::add_two(2));
}
测试目录文件结构和模块一样,但子目录不被测试提取
tests/integration_test.rs// 文件中 test 注解会被测试提取
tests/common/mod.rs// 文件中不会被测试提取,测试系统不考虑子模块的函数。但是上级测试方法可以调用子级
编写一个命令行程序
use std::env; // 环境变量功能
fn main() {
let args: Vec<String> = env::args ().collect (); // 解析参数
let query = &args [1]; // 提取参数,为啥不是 [0],[0] 是二进制文件路径 "target/debug/exename"
let filename = &args[2];
println!("{:?}", args);
}
注意 std::env::args 在其任何参数包含无效 Unicode 字符时会 panic。如果你需要接受包含无效 Unicode 字符的参数,使用 std::env::args_os 代替。这个函数返回 OsString 值而不是 String 值。
use std::env;
use std::fs::File; //std::fs::File 来处理文件
use std::io::prelude::*; // 而 std::io::prelude::* 则包含许多对于 I/O(包括文件 I/O)有帮助的 trait
let mut f = File::open (filename).expect ("file not found"); // 读取文件
let mut contents = String::new();
f.read_to_string (&mut contents) // 文件内容读取到字符串变量
.expect("something went wrong reading the file");
let query = args [1].clone (); // 简单无脑的 clone 有一定好处,可以不用管生命周期
impl Config {
fn new (args: &[String]) -> Result<Config, &'static str> { // 静态周期 字符串
if args.len() < 3 {
return Err("not enough arguments");
}
let query = args[1].clone(); // clone
let filename = args[2].clone();
Ok (Config { query, filename}) // 返回 Config 结构
}
}
use std::process;// 进程
let config = Config::new(&args).unwrap_or_else(| err| { // 注意 |err| 和闭包有关 将 Err 内部值传给闭包 err 参数
println!("Problem parsing arguments: {}", err); // 这就是闭包参数 err,看起来类似 委托参数类似 c# 的 (err)=>{...}
process::exit (1); // 退出进程
});
line.to_lowercase ().contains (&query) // 转换小写并检查是否含有 & query
env::var ("CASE_INSENSITIVE").is_err (); // 获取环境变量 powershell 中设置环境变量:$env.CASE_INSENSITIVE=1
eprintln!("{:?}", &str1) // 标准错误输出宏
闭包(closures)
c# 叫委托,c 叫函数指针 (最好不要乱叫 免得被大神喷!)
代码特征就是 || 两个竖,两个竖线之间是参数列表。
let 闭包名 = |v1:type, v2:type| -> type {.........................}
类似:c# 的 var fn = (v1, v2) => { ....; return i32; } // 这个写法就是类比一下,方便理解。
(一个方法或函数 在机器码中就是 以某个内存地址开头的一组机器码数据,)
use std::thread; // 线程
use std::time::Duration; // 时间差
fn simulated_expensive_calculation(intensity: u32) -> u32 {
thread::sleep (Duration::from_secs (2)); // 睡 2 秒 这个值得记住
0
}
let expensive_closure = |num, strx| { // 定义一个闭包 委托 函数指针(别瞎叫)
println!("calculating slowly..{}..", strx);
thread::sleep(Duration::from_secs(2));
num
};
let expensive_closure = |num: u32, strx:String| -> u32 {
println!("calculating slowly..{}..", strx);
thread::sleep(Duration::from_secs(2));
num
};
fn add_one_v1 (x: u32) -> u32 { x + 1 } // 这是一组对比,下面的和这个 fn 一致
let add_one_v2 = |x: u32| -> u32 {x + 1}; // 指定参数类型返回类型
let add_one_v3 = |x| {x + 1}; // 不指定类型只写函数体
let add_one_v4 = |x| x + 1 ; // 连大括号都去掉了
闭包无法同时进行两次类型推断
let example_closure = |x| x;
let s = example_closure (String::from ("hello")); // 不抛错
let n = example_closure (5); // 抛错 类型不对,已经 被推断过了。
存放了闭包和一个 Option 结果值的 Cacher 结构体的定义
struct Cacher<T>
where T: Fn (u32) -> u32 // T 就是个闭包,T 的 trait bound 指定了 T 是一个使用 Fn 的闭包。
{
calculation: T,
value: Option<u32>,
}
fn main () { // 内部函数
let x = 4;
fn equal_to_x (z: i32) -> bool { z == x } // 这里 报错 不能使用 x 函数不能上下文用外部值,闭包可以
let equal_to_x = |z| z == x; // 这就可以,捕获其环境并访问其被定义的作用域的变量
let y = 4;
assert!(equal_to_x(y));
}
如果你希望强制闭包获取其使用的环境值的所有权,可以在参数列表前使用 move 关键字。这个技巧在将闭包传递给新线程以便将数据移动到新线程中时最为实用。
fn main() {
let x = vec![1, 2, 3];
let equal_to_x = move |z| z == x; //x 被移动进了闭包,因为闭包使用 move 关键字定义。闭包获取了 x 的所有权,main 不再允许使用 x 。去掉 println! 即可。
println!("can't use x here: {:?}", x); //x 不可用了
let y = vec![1, 2, 3];
assert!(equal_to_x(y));
}
迭代器(iterator)
负责遍历序列中的每一项和决定序列何时结束的逻辑。
let v1 = vec![1, 2, 3];
let v1_iter = v1. iter();
for val in v1_iter {
println!("Got: {}", val);
}
trait Iterator { 迭代器都实现了这个 trait 特性
type Item;
fn nex t(&mut self) -> Option<Self::Item>; // methods with default implementations elided
}
let mut v1_iter = v1.iter (); //v1_iter 需要是可变的
assert_eq!(v1_iter.next (), Some (&1)); //next 之后处理结果
如果我们需要一个获取 v1 所有权并返回拥有所有权的迭代器,则可以调用 into_iter 而不是 iter。
如果我们希望迭代可变引用,则可以调用 iter_mut 而不是 iter。
调用 next 方法的方法被称为 消费适配器(consuming adaptors)
let v1_iter = v1.iter();
let total: i32 = v1_iter.sum (); // 调用 sum 之后不再允许使用 v1_iter 因为调用 sum 时它会获取迭代器的所有权。
let v1: Vec<i32> = vec![1, 2, 3];
let v2: Vec<_> = v1.iter ().map (|x| x + 1).collect (); // 闭包 + 1 后返还给 Vec<...> v2
//collect 方法。这个方法消费迭代器并将结果收集到一个数据结构中。
宏 -- 结构比较复杂较难理解
两种宏:
声明式宏( declarative macro )来进行元编程(metaprogramming);
过程式宏( procedural macro )来自定义 derive traits | 更像函数(一种过程类型)
导入
#[macro_use] // 告诉编译器读取所有模块下的宏,这里就能区分重名
extern crate serde;
一个宏定义(声明式宏)
#[macro_export] // 定义 宏 体
macro_rules! strfrom { // 定义宏名 strfrom ,这个宏是将 一个字符串数组 加空格 组合成一个 String
($( $x:expr),* ) => { // 单边模式 ( $( $x:expr ),* ) 表示 要匹配 *(0 个或多个)个 $x:expr 模式
{
let mut temp_str = String::from (""); // 创建一个字符串变量
$( // 宏替换的块
temp_str.push_str ($x); // 提前换的具体操作
temp_str.push(' ');
)* // 0 个或者多个宏替换块
temp_str // 表达式返回
}
};
}
下面是过程式宏
过程宏接收 Rust 代码作为输入,在这些代码上进行操作,然后产生另一些代码作为输出,而非像声明式宏那样匹配对应模式然后以另一部分代码替换当前代码。
有三种类型的过程宏,不过它们的工作方式都类似。
其一,其定义必须位于一种特殊类型的属于它们自己的 crate 中。这么做出于复杂的技术原因,将来我们希望能够消除这些限制。
其二,使用这些宏需采用类似示例 19-37 所示的代码形式,其中 some_attribute 是一个使用特定宏的占位符。
这篇关于rust 助剂的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!