目录
〇,版本
Rust 语言的版本包括以下三个相互正交的概念:语义化版本、发行版本、Edition 版次
1,语义化版本
其格式为: 主版本号.次版本号.修订号,依次用 句点 隔开。
简单说一下语义版本号递增规则:
主版本号:当做了不兼容的 API 修改
次版本号:当做了向下兼容的功能性新增
修订号:当做了向下兼容的问题修正。
2,发行版本
master -> Nightly
beta -> Beta
stable -> Stable
3,Edition 版次
2015 Edition
2018 Edition
2021 Edition
一,输入输出
1,输出
println!("Hello, World");
println!("{}",format!("hello world"));
println用于输出到控制台
format用于输出到String对象中
2,输入
(1)读取一行
fn foo()->String{
let mut buf = String::new();
stdin().read_line(&mut buf).unwrap(); //读取一行 \n结束
return buf;
}
或者:
fn foo()->String{
let mut buf = String::new();
stdin().lock().read_line(&mut buf).unwrap(); //读取一行 \n结束
return buf;
}
或者:
fn foo()->String{
let mut buf = String::new();
buf = stdin().lines().into_iter().next().unwrap().unwrap(); //读取一行 \n结束
return buf;
}
(2)读取一整段
fn foo()->String{
let mut buf = String::new();
stdin().lock().read_to_string(&mut buf).unwrap(); //读取一段 EOF结束
return buf;
}
二,函数
1,main函数
fn main() {
println!("Hello, World");
}
2,普通函数
(1)void函数
fn myPrint(){
println!("{}", 1234);
}
fn main() {
myPrint();
}
(2)带返回值的函数
可以这样:
fn f()->i32{
let x=123;
return x+1;
}
fn main() {
println!("{}",f());
}
也可以这样:
fn f()->i32{
let x:i32 = 1234;
x+1
}
还可以这样:
fn f()->i32{
123456
}
也就是说,大括号{}里面可以当成一个大的表达式,而表达式是可以直接return的,不要带分号结尾,而return语句要带分号结尾。
3,调用外部函数
以调用C语言为例:
use std::os::raw::c_double; // 64位
extern "C" {
fn sqrt(num: c_double) -> c_double;
}
fn main() {
let mut y = 64.0;
assert_eq!(unsafe { sqrt(y) }, 8.0);
}
调用外部函数,要用unsafe块包起来。
4,lambda表达式
错误代码:
fn main() {
let f=|x|x;
let s =f(String::from("hello"));
let n=f(5);
print!("end");
}
正确代码:
fn main() {
let f=|x|x;
let s =f(String::from("hello"));
let n=f(String::from("??"));
print!("end");
}
编译器会根据第一次调用lambda表达式的实参,推导出入参类型。
三,标识符、变量
1,标识符
标识符的BNF:
IDENTIFIER_OR_KEYWORD :
XID_Start XID_Continue*
| _
XID_Continue+
RAW_IDENTIFIER : r#
IDENTIFIER_OR_KEYWORD Except crate
, self
, super
, Self
NON_KEYWORD_IDENTIFIER : IDENTIFIER_OR_KEYWORD Except a strict or reserved keyword
IDENTIFIER :
NON_KEYWORD_IDENTIFIER | RAW_IDENTIFIER
第一句是哪些字符串可以表示IDENTIFIER_OR_KEYWORD,字符范围特别广,但是#不在其中,可以这么搞:
let 中国 = 520;
第二句是IDENTIFIER_OR_KEYWORD 里面除了crate, self, super, Self之外,剩下的都可以加r#前缀,变成原始标识符
比如调用c语言的match,不能直接用match,可以用r#match来调用。
第三句是非关键字的标识符,即NON_KEYWORD_IDENTIFIER里面去掉关键字的那部分。
第四句是标识符包括非关键字的标识符、原始标识符两种。
2,变量绑定、let、mut
let x = "what";
println!("{}",x);
绑定行为类似于引用,所以这里面的x是不可修改的。
错误代码:
let x = "what";
x="rrr";
正确代码:
let mut x = "what";
x="rrr";
3,变量作用域
fn main() {
let x=5;
let x=x+1;
{
let x=x*2;
println!("{}",x);
}
println!("{}",x);
}
输出:
12 6
代码等价于:
fn main() {
let x=5;
let x2=x+1;
{
let x3=x2*2;
println!("{}",x3);
}
println!("{}",x2);
}
也就是说,同一个作用域内,同样的变量名,重复进行绑定,就取代了之前的绑定,
而大括号{}则自成作用域,作用域内可以重新绑定,出了作用域又退回之前的绑定状态。
4,关键字
rust的关键字分类3类:
- 严格关键字:已经有特殊用途的关键字
- 保留关键字:先保留,以后可能有特殊用途的关键字
- 弱关键字:只在特定上下文有特殊用途,在其他上下文也可以作为变量名
(1)严格关键字
- as - 强制类型转换,消除特定包含项的 trait 的歧义,或者对 use 和 extern crate 语句中的项重命名
- async - 返回一个 Future 而不是阻塞当前线程( 2018版新增)
- await - 暂停执行直到 Future 的结果就绪( 2018版新增)
- break - 立刻退出循环
- const - 定义常量或不变裸指针(constant raw pointer)
- continue - 继续进入下一次循环迭代
- crate - 链接(link)一个外部 crate 或一个代表宏定义的 crate 的宏变量
- dyn - 动态分发 trait 对象
- else - 作为 if 和 if let 控制流结构的 fallback
- enum - 定义一个枚举
- extern - 链接一个外部 crate 、函数或变量
- false - 布尔字面值 false
- fn - 定义一个函数或 函数指针类型 (function pointer type)
- for - 遍历一个迭代器或实现一个 trait 或者指定一个更高级的生命周期
- if - 基于条件表达式的结果分支
- impl - 实现自有或 trait 功能
- in - for - 循环语法的一部分
- let - 绑定一个变量
- loop - 无条件循环
- match - 模式匹配
- mod - 定义一个模块
- move - 使闭包获取其所捕获项的所有权
- mut - 表示引用、裸指针或模式绑定的可变性
- pub - 表示结构体字段、impl 块或模块的公有可见性
- ref - 通过引用绑定
- return - 从函数中返回
- Self - 定义或实现 trait 的类型的类型别名
- self - 表示方法本身或当前模块
- static - 表示全局变量或在整个程序执行期间保持其生命周期
- struct - 定义一个结构体
- super - 表示当前模块的父模块
- trait - 定义一个 trait
- true - 布尔字面值 true
- type - 定义一个类型别名或关联类型
- unsafe - 表示不安全的代码、函数、trait 或实现
- use - 引入外部空间的符号
- where - 表示一个约束类型的从句
- while - 基于一个表达式的结果判断是否进行循环
(2)保留关键字
- abstract
- become
- box
- do
- final
- macro
- override
- priv
- try
- typeof
- unsized
- virtual
- yield
(3)弱关键字
union
5,生命周期
(1)生命周期推导
错误代码:
fn the_max(a:&i32,b:&i32)->&i32 {
if a>b{
return a;
}
b
}
错误原因:编译器无法推导返回值的生命周期是同a还是同b
解决办法:
在所有造成编译器无法自动推导生命周期的地方,都需要显式标注生命周期进行辅助。
(2)生命周期标注符
正确代码:
fn the_max<'a>(a:&'a i32,b:&'a i32)->&'a i32{
if a>b{
return a;
}
b
}
这里的the_max<'a>类似于泛型函数,'a类似于泛型参数,但实际上它是生命周期标注符,表示该类型具有该生命周期。
'表示这是一个生命周期标注符,a是标注符的名称,一般用非常简短的纯小写字母表示。
(3)含引用的结构体
如果一个结构体中有引用成员,则必须有生命周期标注符。
struct S<'a>{
x:&'a i32
}
fn main() {
let x=5;
let s=S{x:&x};
}
这个标志表示,x的生命周期至少包含了S的生命周期。
(4)静态生命周期
字面量具有静态生命周期'static
静态生命周期是最大的生命周期,其他任何生命周期都是它的一部分。
(5)生命周期子类型
生命周期a是b的子类型指的是,生命周期a至少包含生命周期b,即满足生命周期a的类型可以直接当成生命周期b使用。
fn f<'a:'b,'b>(x:&'a i32,y:&'b i32)->&'b i32{
if x>y{
return y;
}
return x;
}
(6)单入参函数的生命周期自动推导
fn f(s:&str)->&str {
s
}
fn f2(x:&i32)->&i32 {
if x > &0 {
return &x;
}
return &5;
}
对于只有一个引用入参的函数,如果返回值是引用类型,则返回值要么来自引用入参,要么来自静态生命周期,所以返回值的生命周期被自动推导成和入参相同。
这是一条编译器自动推导规则,有了这条规则,这个例子中就不需要生命周期标注符了。
当然,也可以标注:
fn f<'a>(s:&'a str)->&'a str {
s
}
(7)带self参数函数的生命周期自动推导
struct S{
x: i32
}
impl S{
fn f(&self,x:&i32)->&i32{
&self.x
}
}
带self参数函数的返回值中的引用类型,自动推导出和self相同的生命周期。
(8)生命周期编译规则总结
生命周期推导规则有这么几条:
- 对于每一个引用入参,如果有标注符,则以标注符为准,如果没有,则获得独自的生命周期。
- 若函数入参中只有一个引用类型,那么返回值中的引用类型自动推导出和入参相同的生命周期。
- 若函数入参中含有self参数,那么返回值中的引用类型自动推导出和self相同的生命周期。
- 若函数入参中有多个引用,且不含self,那么必须标注返回值的生命周期(即使所有入参的生命周期都一致也不能自动推导)。
编译器如何使用上述规则?
首先进行推导,判断函数本身能否编译,这一步和调用点无关。
其次如果有标注符,再逐一检查调用点,判断是否满足标注符的约束。
四,表达式和流程控制
1,match
match用于流程控制,和c++的switch case有点类似。
(1)按值匹配、按表达式匹配
fn main() {
let x=1;
let y=match x{
1=>2,
x =>x*x
};
assert_eq!(y,2);
println!("end");
}
这里有2种匹配,如果是数值1,则得到2,如果是表达式x,则得到x*x
(2)match语句的类型
上面的例子中,match语句整体是一个整数类型。
下面这个例子中,match语句整体的类型取决于几个函数的类型
fn f()->f32{
return 0.1;
}
fn f2()->f32{
return 0.2;
}
fn main() {
let x=1;
let y=match x{
1=>f(),
x =>f2()
};
assert_eq!(y,0.1);
println!("end");
}
下面这个例子中,match语句整体是一个单元元组类型。
fn main() {
let x=1;
let y=match x{
1=>println!("2"),
x =>println!("{}",x*x)
};
assert_eq!(y,());
println!("end");
}
实际上,这3个例子中,表达式、函数调用和宏调用都是语句,按照语句的类型进行推导即可。
(3)通配符
match语句必须满足,所有的可能性都已经包含了,否则会编译失败。
通配符可以用于构造一定能匹配的情况:
fn main() {
let x=7;
match x{
x if x<5 => println!("x<5"),
x if x>5 => println!("x>5"),
_ => println!("else")
};
println!("end");
}
其中通配符_表示所有情况都可以匹配,_本身是没有类型的,甚至在一个match语句中,不同位置的_对应不同类型的通配符也是可以的。
(4)条件匹配
let x=Some(7);
match x{
Some(x) if x<5 => println!("x<5"),
Some(x) if x>5 => println!("x>5"),
None => println!("none"),
_ => println!("else")
};
这个例子和上一个差不多,Some(x)只是更复杂的表达式而已。
表达式后面的if条件语句,和左边的匹配表达式构成一个整体,只有这2个都满足时,这一条才能match匹配成功。
(5)合并分支
fn main() {
let x=(3,4);
match x{
(x,1)|(_,x) if x>0 => println!("x<5"),
_ => println!("else")
};
println!("end");
}
这里的或符号相当于把2种情况合并了,这个代码等价于:
fn main() {
let x=(3,4);
match x{
(x,1) if x>0 => println!("x<5"),
(_,x) if x>0 => println!("x<5"),
_ => println!("else")
};
println!("end");
}
五,模块
1,模块结构
(1)1个文件就是1个模块
(2)模块可以嵌套,1个模块可以包含多个并列的子模块
所以在1个文件模块中,可以用mod定义多个模块
(3)模块可以多级嵌套
所以所有的模块构成一棵树
(4)可以定义多个文件组成的模块
有2种形式去定义多文件模块:
六,异步编程
1,async
async用于定义一个可以并发的任务,await用于触发任务并发执行。
正确代码:
async fn app(){
}
(1)extern中的函数
错误代码:
extern "C" {
async fn f(); //无法编译
}
错误原因:functions in `extern` blocks cannot have qualifiers,即extern中的函数声明不能添加修饰符
(2)trait中的函数
错误代码:
trait MyTrait{
async fn f(){
}
}
错误原因:trait中的函数不能用async修饰
(3)异步函数调用异步函数
正确代码:
async fn foo1(){}
async fn foo2(){}
async fn foo(){
foo1().await;
foo2().await;
}
(4)递归异步函数
错误代码:
async fn bar(){
bar().await;
bar().await;
}
七,rust内存安全
消除c++的未定义行为,如整数溢出变成确定行为
painc是最后一道安全防御措施,如内存越界和空指针访问变成panic
未初始化内存访问、野指针访问、悬空指针 在rust里面不存在
c++中内存分配和初始化是可以分开的,rust是合二为一的,中间状态不暴露给开发者。代价就是需要更多的栈空间。
八,编程规范
1,命名格式
大驼峰UpperCamelCase:类型、枚举成员、非原生类型、Trait
小写下划线snake_case:结构体成员、联合体成员、包和模块、函数和宏、条件编译Features
大写下划线SCREAMING_SNAKE_CASE:静态变量、常量
简短大驼峰:模板类型参数T
简短纯小写:生命周期标注符'a
PS:其他变量就是非静态变量、常量、枚举成员的所有变量。
2,常用命名
构造函数用with_***命名
转换构造函数用from_***命名
as_、to_、into_前缀分别指示了不同的内存代价和所有权转移。
3,常量类型不应具有内部可变性
内部可变性包括atomic*** , Cell , RefCell , UnsafeCell , Mutex , RwLock , OnceLock 等
4,类型转换
数据类型从高往低转,用低类型::try_from()
数据类型从低往高转,直接用高类型::from()
5,数值字面量要添加明确的类型标识
RT
6,宏里面的多条匹配规则,应该按照从小到大排序
RT
7,外部函数边界FFI
禁止调用C不可重入函数
禁止调用外部内存不安全函数
九,同名辨析
列举几种同名或接近同名的token造成困扰的例子。
1,vec、Vec
fn main() {
use std::vec::Vec;
let vec:Vec<i32> = vec![1,2,3];
println!("end");
}
其中,包vec是小写下划线,结构体Vec是大驼峰,变量vec是小写下划线,宏vec是小写下划线。
所以在这2行代码中,这4个token的含义都不一样,依赖语法进行区分。
2,一个类型拥有多个同名函数
3,隐式转换导致拥有多个同名函数
参考Deref
4,trait内的函数和一个单独的函数是同名的
例如,Drop中的drop函数和泛型的drop函数,参考drop
5,标准库的不同包里面的同名数据结构
fn main() {
{
let x:Result<i32,i32>=Err(1);
}
{
use std::fmt::Result;
let x:Result=Err(core::fmt::Error);
}
print!("end");
}
其中,fmt::Result是Result的特化,但是名字还是一样的。。。
特化源码:
pub type Result = result::Result<(), Error>;
其中第一个是单元元组,第二个是单元元组结构体。