Rust 快速入门
- 初始化项目
- 基础
- 变量 - 常量
- 数据类型
- 函数
- 注释
- 控制流
- 所有权
-
- Slice 类型
-
- 结构体
-
- 枚举
-
- 模式匹配
- match
- 通配模式 和 _ 占位符 和 多个匹配 和 守卫条件
- if let 条件表达式 - 语法糖
- while let 条件循环
- 模式匹配 核心理解
- 模式语法
- 项目管理
-
- 错误处理
-
- 常见集合
-
- 泛型
- trait
- 生命周期
- 参考
博主是边学边写,有错误请宝贝们指出
初始化项目
编辑器推荐: IDEA + Rust插件
我用的linux , window如何建项目,百度吧亲~
cargo new 项目名
建好后就是这样的,很漂亮是吧
基础
Rust官方学习文档(中文): https://kaisery.github.io/trpl-zh-cn/title-page.html
变量 - 常量
基础
let a:i32=1;
let mut b=1;
const c:i32=1;
隐藏特性
可以说就是重新定义了一个变量,只是名字一样而已,当然它并没有破坏作用域
按官方的话说就是: Rustacean 们称之为第一个变量被第二个 隐藏 了,这意味着程序使用这个变量时会看到第二个值
let x = 5;
let x = x + 1;
{
let x = x * 2;
println!("{}", x);
let x="abc";
println!("{}", x);
}
println!("{}", x);
数据类型
长度(单位:bit) | 有符号 | 无符号 |
---|
整型(默认:i32) | | |
8-bit | i8 | u8 |
16-bit | i16 | u16 |
32-bit | i32 | u32 |
64-bit | i64 | u64 |
128-bit | i128 | u128 |
arch | isize | usize |
浮动数(默认:f64) | | |
32-bit | f32 | |
64-bit | f64 | |
字符类型 char
,大小4字节
布尔类型 bool
let a=123_456i64;
let a=0xffi16;
let a:i8 =0o7_i8;
let a=0b1111_0000_1;
let a:u8=b'a';
元组类型
元组是一个将多个其他类型的值组合进一个复合类型的主要方式。元组长度固定:一旦声明,其长度不会增大或缩小
没有任何值的元组 () 是一种特殊的类型,只有一个值,也写成 () 。该类型被称为 单元类型(unit type),而该值被称为 单元值(unit value)。如果表达式不返回任何其他值,则会隐式返回单元值
我喜欢把 单元值
叫做 空元组
let a:(i32,f32)=(1,2.2);
let (z,x)=a;
let (z,x,c)=(2,2.2,true);
fn f() -> (i32,bool){
return (1,false);
}
数组类型
与元组不同,数组中的每个元素的类型必须相同。Rust 中的数组与一些其他语言中的数组不同,Rust中的数组长度是固定的
let a =[1,2,3];
let a: [i32; 5] = [1, 2, 3, 4, 5];
let a = [1;3];
函数
表达式和语句这个概念在Rust比较重要
语句(Statements)是执行一些操作但不返回值的指令。表达式(Expressions)计算并产生一个值
如果在表达式的结尾加上分号,它就变成了语句,而语句不会返回值
let a:i32={1};
用大括号创建的一个新的块作用域也是一个表达式
在函数签名中,必须 声明每个参数的类型
不能确定
,在Rust中{}就是一个表达式,而表达式内部结尾是一个表达式的话,就返回这个表达式,如果没有,则返回空元组
fn 函数名(参数名:参数类型,...) -> 返回类型 {
}
fn f(x:i32)->i32{
return 1;
1
}
关于函数式
|参数名:参数类型| 表达式
let f = |x:i32| x+1 ;
let f:fn(i32)->i32=|x| x+1;
let f:fn(i32)->i32=|x:i32| x+1 ;
let f:fn(i32)->i32=|x:i32| {
return x+1;
x+1
};
let f:fn(fn(i32)->i32)->fn()->();
f = |af:fn(i32)->i32| {
||()
};
注释
控制流
if 表达式
if 本身也是表达式
if true{
}else if true{
}else{
}
let a = if true { }else { 1 };
循环体
loop
无限循环,不知道它出现的意义是啥子,有懂的宝贝,评论一下,蟹蟹
'a:loop {
break 'a;
}
循环返回
let mut a;
a = 'a:loop {
break 'a;
};
a = loop{
break 1;
};
while
正常循环
'a:while true{
break 'a;
}
for
遍历集合
let s=[1;5];
for v in s{
println!("{}",v);
}
for v in 0..5{
println!("{}",s[v]);
}
for v in (0..5).rev(){
println!("{}",s[v]);
}
所有权
我认为这玩意还是看官方文档的好,快速入门主要还是怕我以后忘记了,然后快速回忆的,不会描述过多的细节
所有权_官方文档
所有权(系统)是 Rust 最为与众不同的特性,它让 Rust 无需垃圾回收(garbage collector)即可保障内存安全。因此,理解 Rust 中所有权如何工作是十分重要的
我喜欢把所有权
叫拥有权
,所有者
叫拥有者
,因为所有
这词有点难理解,不过习惯就好
栈(Stack)是固定大小 , 堆(Heap)可变大小
let a="123";
let a=String::from("123");
内存在拥有它的变量离开作用域后就被自动释放
{
let s="123";
}
所有权规则
Rust 中的每一个值都有一个被称为其 所有者(owner)的变量。
值在任一时刻有且只有一个所有者。
当所有者(变量)离开作用域,这个值将被丢弃。
移动
将值赋给另一个变量时移动它。当持有堆中数据值的变量离开作用域时,其值将通过 drop 被清理掉,除非数据被移动为另一个变量所有
let a="123";
let b=a;
let a = String::from("123");
let b = a;
克隆
如果我们 确实 需要深度复制 堆上的数据,而不仅仅是栈上的数据,可以使用一个叫做 clone()
的通用函数
let a = String::from("123");
let b = a.clone();
所有权与函数
学这玩意,你必须了解 移动 和 栈堆
let s=String::from("123");
f1(s);
fn f1(a:String){
}
let i=1;
f2(i);
println!("{}",i);
fn f2(x:i32){
}
返回值与作用域
和 所有权与函数 一个道理
let s=f1();
println!("{}",s);
let b=f2(s);
println!("{}",b);
fn f1()-> String{
String::from("123")
}
fn f2(a:String )->String{
a
}
引用与借用
& 符号是 引用 , * 符号是 解引用
Rust引用 和 C指针很像 , 在官方文档第15章<<智能指针>>,我估计和C指针概念差不多
& 符号就是 引用,它们允许你使用值但不获取其所有权
我们将创建一个引用的行为称为 借用(borrowing)
C
int i=1;
int *pi=&i;
printf("%d%d",i,*pi);
Rust
let i:i32=1;
let pi:&i32=&i;
println!("{}{}",i,pi);
可变引用
引用规则:
在任意给定时间,要么 只能有一个可变引用,要么 只能有多个不可变引用。
引用必须总是有效的。
一个引用的作用域从 声明的地方开始一直持续到最后一次使用为止
let a:&mut i32;
let mut b:i32=1;
a=&mut b;
*a=9;
let a:&i32;
a=&b;
println!("{}",a);
可变引用有一个很大的限制:在同一时间只能有一个对某一特定数据的可变引用 ; 这个限制的好处是 Rust 可以在编译时就避免数据竞争
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s;
println!("{}", r2);
也不能在拥有不可变引用的同时拥有可变引用
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
let r3 = &mut s;
println!("{}",r3);
总而言之就是 存在一个可变引用时 不能在出现一个引用(可变或不可变)
以上2条几乎没机会触发,毕竟你没必要同时需要多个可变引用,也没必要存在可变引用,需要不可变引用
Slice 类型
slice 是一类引用,所以它没有所有权
语法: &变量[a…b] , 区间[a,b)
字符串 slice
的类型声明写作 &str
字符串字面值被储存在二进制文件 , &str
它是一个指向二进制程序特定位置的 slice。这也就是为什么字符串字面值是不可变的;所以 &str
是一个不可变引用
let a:&str="0123456789";
let b:String=String::from("012345");
println!("{}",&b[..]);
println!("{}",&b[..3]);
println!("{}",&b[3..]);
println!("{}",&b[0..3]);
其他类型的 slice
…给个例子,其他省略
let a:[i32;5] = [1, 2, 3, 4, 5];
let slice:&[i32] = &a[1..3];
结构体
至于概念就不描述了,来学Rust的估计也都是些 大佬 了
struct 结构体名 {
int:i32,
long:i64
}
let a = 结构体名 {
int: 1,
long:2
};
let b = 结构体名 {
int:5,
..a
};
struct 元组结构体名(i32, i32, i32);
let a= 元组结构体名(1,2,3);
struct 类单元结构体名;
方法
概念就不描述了
struct 结构体名 {
int:i32,
long:i64
}
impl 结构体名 {
fn new(int:i32,long:i64)->结构体名{
结构体名 {
int,
long
}
}
fn f(self:&self){}
fn add(&self)->i64{
self.long + self.int as i64
}
fn change(&mut self,int:i32,long:i64){
self.int=int;
self.long=long;
}
fn 自杀(self){
}
}
fn main() {
let mut a:结构体名= 结构体名::new(1,2);
a.f();
println!("{}",a.add());
a.change(5,5);
println!("{}",a.add());
a.自杀();
}
可以定义多个impl块
枚举
可以将任意类型的数据放入枚举成员中,枚举类型也可以
enum B{
bb
}
enum ABC{
A,
B(B),
C(i32,String),
D{x:i32,y:i32},
E()
}
fn main() {
let a = ABC::A;
let b = ABC::B(B::bb);
let c = ABC::C(1,String::from("a"));
let d = ABC::D {x:1,y:2};
let e = ABC::E();
}
枚举也可以定义方法
impl ABC{
fn a(&self){
}
}
C风格用法
enum Color {
Red = 0xff0000,
Green = 0x00ff00,
Blue = 0x0000ff,
}
enum Number {
Zero,
One,
Two,
}
fn main() {
println!("{}",Color::Red as i32);
println!("{}",Number::Zero as i32);
}
Option 枚举
Option 类型应用广泛因为它编码了一个非常普遍的场景,即一个值要么有值要么没值
Rust 并没有很多其他语言中有的空值功能。空值(Null )是一个值,它代表没有值。在有空值的语言中,变量总是这两种状态之一:空值和非空值。
Rust 并没有空值,不过它确实拥有一个可以编码存在或不存在概念的枚举。这个枚举是 Option<T>
,而且它定义于标准库中,如下:
enum Option<T> {
None,
Some(T),
}
不能确定!
,只有Option<T>
才存在null的概念,其他类型变量都不存在,也就是你不用担心非Option
运行时出现null情况,如果有null那你程序跑不起来的
模式匹配
对于 模式匹配
这个概念,我也有点模糊,坐等学到后面自然懂,哈哈
match
match 表达式必须是 穷尽(exhaustive)的,意为 match 表达式所有可能的值都必须被考虑到
模式由如下一些内容组合而成:
字面值
解构的数组、枚举、结构体或者元组
变量
通配符
占位符
语法:
match 值 {
模式 => 表达式
}
通配模式 和 _ 占位符 和 多个匹配 和 守卫条件
例子:
enum ABC {
A,
B,
C,
D(i32)
}
fn main() {
let a=ABC::D(9);
let x =match a {
ABC::A | ABC::B => 1,
ABC::D(c) if c > 0 => {c},
_ => 0
};
println!("{}",x);
let a=0;
let x = match a {
b => {
b+1
}
};
println!("{}",x);
}
if let 条件表达式 - 语法糖
if let 表达式的缺点在于其穷尽性没有为编译器所检查,而 match 表达式则检查了
语法:
if let 模式 = 值 表达式
你还可以使用 else if let , 甚至和 if 一起使用
例如:
let a=ABC::B;
let x = if let ABC::A = a { 0 }
else if let ABC::B = a { 1 }
else if 1==1 { 2 }
else {3};
println!("{}",x);
我认为就是加强了if
, 因为if只能接受bool类型,没有模式匹配那么强大
while let 条件循环
只要模式匹配就一直进行 while 循环
语法:
while let 模式 = 值 {
}
模式匹配 核心理解
关于提高理解程度,建议读官方的文档(可反驳性)
模式有两种形式:refutable(可反驳的)和 irrefutable(不可反驳的)
函数参数、 let 语句和 for 循环只能接受不可反驳的模式,因为通过不匹配的值程序无法进行有意义的工作。if let 和 while let 表达式被限制为只能接受可反驳的模式,因为根据定义他们意在处理可能的失败:条件表达式的功能就是根据成功或失败执行不同的操作
match匹配分支必须使用可反驳模式,除了最后一个分支需要使用能匹配任何剩余值的不可反驳模式
let a=1;
if let Some(x) = a_value { };
模式语法
官方文档入口,哦耶~
用 .. 忽略剩余值
@ 绑定
使用 @ 可以在一个模式中同时测试和保存变量值
上面已经描述一部分了,具体见官方文档
项目管理
模块
mod
模块关键字
pub
代表公共,默认私有,除了枚举可以影响孩子,其他都需要单独声明
crate
是根模块
孩子可以访问父亲,私有的也可以
在Rust中,模块就像文件系统一样,它们有 路径,可以通过use
将功能引入到当期作用域
在模块内,我们还可以定义其他的模块,模块还可以保存一些定义的其他项,比如结构体、枚举、常量、特性、或者函数
绝对路径从 crate 根开始,以 crate 名或者字面值 crate 开头。
相对路径从当前模块开始,以 self、super 或当前模块的标识符开头。
mod A {
struct Breakfast {
pub toast: String,
seasonal_fruit: String,
}
mod B {
use crate::A;
use super::super::A;
pub fn b(){
A::a();
}
}
fn a(){
B::b();
}
}
use
使用 use 把对应功能 引入当前作用域
println!("{}",std::cmp::min(1,2));
println!("{}",cmp::min(1,2));
println!("{}",min(1,2));
println!("{}",min(1,2));
use std::cmp::{min, max};
use std::{cmp};
use std::{cmp::min, cmp::max};
use std::io;
use std::io::Write;
use std::io::{self, Write};
use std::cmp as 别名
重导出 pub use
fn main() {
B::a();
}
mod A{
pub fn a(){}
}
mod B{
pub use super::A::a;
}
模块分割进不同文件
我的理解是 mod 模块名;
是声明子模块,pub mod 模块名;
是暴露(公开,公共)子模块
根模块是main.rs
或 lib.rs
, mod.rs
估计就是根据目录名来决定模块名,还有一种方法,就是在/src
下建 A.rs
,不过没这个方便好看
这个就和 普通的差不多,只不过是把 内容写到文件去了,上图和下文差不多的意思,但是构建项目,你不可能全塞到一个文件里
mod A {
pub mod A_123 {
pub fn a123(){
}
}
pub fn f(){
}
}
fn main(){
A::f();
A::A_123::a123();
}
错误处理
不可恢复错误
panic!
不可恢复错误(异常)
总而言之调用 panic!
宏,程序就会死亡
比如你程序生病了,你不想程序死亡,你可以选择治疗它,那治疗就是 可恢复错误
当然,有必死的情况,比如数组越界,所以你得注意程序的逻辑
可恢复错误
enum Result<T, E> {
Ok(T),
Err(E),
}
其实这和Option
是一个逻辑,返回不同的情况,作出不同的处理
在概念上,就完全可以把它说成错误,虽然它是枚举,就比如 返回1是正常,返回0是异常,只是形式不同
Result就是告诉你,它可能无法达到预期,达到预期就OK,没有达到就Err
if let Ok(file) = File::open("a.txt") {
print!("ok");
}else if let Err(e) = File::open("a.txt"){
println!("err");
match e.kind() {
ErrorKind::NotFound => 0 ,
_ => ()
}
}
让我们的代码在精简一点
let f = File::open("a.txt").unwrap();
let f = File::open("a.txt").expect("Err");
看看源码
通过源码我们看到,就是通过方法简化了 match
,并且调用了panic!
如果你并不想在当前函数处理错误,你可以选择返回错误,让调用当前函数者去处理 , 比如上面的File::open()
就是返回错误,让调用者去处理
? 运算符 - 语法糖
实现了 FromResidual
的类型的函数中可以使用 ?
运算符
Result
和 Option
都实现了
Result
Err就返回
Option
None就返回
fn f() -> Result<String , io::Error>{
let a:File = File::open("123.txt")?;
File::open("123.txt")?.read_to_string(&mut String::from("132"))?;
Ok(String::from("ok"))
}
常见集合
Vec
Vec<T>
的功能就类似 java的 List<T>
一样, 泛型,可变成数组
vec!
宏,提供方便
let mut s:Vec<i32> = Vec::new();
s.push(3);
s.push(3);
s.push(3);
let s = vec![3,3,3];
let s = vec![3;3];
for i in &s {
println!("{}",i);
}
println!("{}",&s[0]);
if let Some(i) = s.get(55) {
println!("{}",i);
} else {
println!("None!");
}
常用操作
let mut s = vec![0,1,2,3,4];
s.sort();
s.push(5);
println!("{}",s.len());
s.remove(5);
println!("{}",s.get(0).unwrap());
println!("{}",&s[0]);
println!("{}",s.pop().unwrap());
for v in &s{
println!("{}",v);
}
s[0]=111;
println!("{}",s.contains(&111));
println!("{}",s.is_empty());
s.retain(|&i| i%2!=0);
println!("{:?}",s);
HashMap
HashMap<K, V>
哈系表
常用操作
use std::collections::HashMap;
let mut map = HashMap::new();
map.insert("a",1);
map.insert("a",2);
map.entry("a").or_insert(11);
*map.get_mut("a").unwrap() = 9;
println!("{}",map.get("a").unwrap());
for (key, value) in &map {
println!("{}: {}", key, value);
}
泛型
概念就不描述了
<T: trait> 可以限制泛型
fn f<T>(t:&T)->&T{
t
}
enum Option<T> {
Some(T),
None,
}
struct ABC<T,R>{
x:T,
y:R
}
impl<T> ABC<T,i32> {
fn new_T_i32(t:T) -> ABC<T,i32>{
ABC { x:t, y:0 }
}
fn x(&self) -> &T{
&self.x
}
fn y(&self) -> i32{
self.y
}
}
我爱了,特别是方法泛型,太美了~
trait
这就类似与java的接口,俗称定一堆公共行为
不过这玩意可比java的接口强太多了
孤儿规则: 不能为外部类型实现外部 trait (这条规则确保了其他人编写的代码不会破坏你代码)
trait A{
fn f(&self)->usize;
fn f2(&self){
println!("默认实现{}",self.f());
}
}
impl A for String {
fn f(&self) -> usize {
self.len() + 100
}
}
fn main() {
let s=String::from("123");
println!("{}",s.f());
s.f2();
}
fn f(i:&impl A) -> &impl A{
i
}
fn f2<T: A>(i:&T){
}
fn f3(i:& (impl A + B)){
}
fn f4<T: A+B>(i:&T){
}
fn f5<T>(i:&T)
where T:A+B+C+... {
}
trait ToString {
fn a(&self);
}
impl<T: Display> ToString for T {
fn a(&self) {
println!("aa");
}
}
fn main() {
1.a();
}
你没有看错,它通过实现了特定的trait的类型 有条件地实现了trait
意思就是 实现了 Display 的全部类型 都实现一遍 ToString ,所以 1.a() 是没有问题的, 太nb了,太爱了
这意味着 你可以随时随地给多个trait增加trait,就类似与给它们增加一个父亲一样,它们这些trait都有父亲trait的行为
生命周期
Rust 生命周期 - 链接
参考
官方文档
30分钟学习-Rust :英文文档
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)