Rust编程语言从入门到放弃
Rust是一门系统编程语言(SystemsProgrammingLanguage),兼顾安全(Safety)、性能(Speed)和并发(Concurrency)。Rust作为一门底层的系统编程语言,理论上,使用C/C++的领域都可以使用Rust实现,例如对硬件需要精细控制的嵌入式编程、对性能要求极高的应用软件(数据库引擎、浏览器引擎,3D渲染引擎等)。相对于C/C++的系统性缺陷(内存管理不当造成的安全漏洞),Rust通过所有权(Ownership)机制在编译期间确保内存安全,无需垃圾回收(GarbageCollection,GC),也不需要手动释放内存。
1.HelloWorld1.1安装Rust在线安装
Windows:下载,自动引导安装。
Linux:curl–proto‘=https’–|sh
离线安装
下载独立安装包
Windows:下载.msi文件,双击安装即可。
Linux:下载.文件,解压后,执行即可。
查看版本
$(4560ea7882019-11-04)$(1c6ec66d52019-09-30)1.2第一个Rust程序
fnmain(){println!("Hello,world!");}使用fn声明函数。与大部分编程语言一致,main()是Rust程序的入口。println!表示打印文本到控制台,!表示这是一个宏(macro),而非一个函数(function)。
保存为hello_,rs为Rust语言的后缀。
编译:rustchello_。
执行:./hello_world(Linux),hello_(Windows)
尝试下println!更多的用法。
fnmain(){println!("{},{}!","Hello","world");//Hello,world!println!("{0},{1}!","Hello","world");//Hello,world!println!("{greeting},{name}!",greeting="Hello",name="world");//Hello,world!lety=String::from("Hello,")+"world!";println!("{}",y);//Hello,world!}以上代码将输出
Hello,world!Hello,world!Hello,world!Hello,world!1.3使用Cargo
为了方便之后的调试和学习,先介绍Rust内置的包管理和构建系统Cargo,是Rust的社区仓库。
创建新项目:cargonew
编译:cargobuild
运行:cargorun
更新项目依赖:cargoupdate
执行测试:cargotest
生成文档:cargodoc
静态检查:cargocheck
新建二进制(Binary/Executable)项目
$cargonewtutu--bin$cdtututree├──└──src└──
在中写入
fnmain(){println!("Hello,Cargo!");}在项目目录下执行cargorun
$cargoruntutugit:(master)✗(/xxx/demo/tutu)Finisheddev[unoptimized+debuginfo]target(s)`target/debug/tutu`Hello,Cargo!
新建Library项目
$cargonewtutu--lib$cdtututree├──└──src└──
是工程的描述文件,包含Cargo所需的所有元信息。
src放置源代码。
/是入口文件。
运行cargorun或cargobuild,可执行文件将生成在target/debug/目录,运行cargobuild–release,可执行文件将生成在target/release/。
2基本概念2.1注释///外部注释modtest{//行注释/*块注释*/}modtest{//!包/模块级别的注释//}2.2变量局部变量
Rust中变量默认是不可变的(immutable),称为变量绑定(Variablebindings),使用mut标志为可变(mutable)。
let声明的变量是局部变量,声明时可以不初始化,使用前初始化即可。Rust是静态类型语言,编译时会检查类型,使用let声明变量时可以省略类型,编译时会推断一个合适的类型。
//不可变letc;leta=true;letb:bool=true;let(x,y)=(1,2);c=12345;//可变letmutz=5;z=6;
全局变量
rust中可用static声明全局变量。用static声明的变量的生命周期是整个程序,从启动到退出,它占用的内存空间是固定的,不会在执行过程中回收。另外,static声明语句,必须显式标明类型,不支持类型自动推导。全局变量在声明时必须初始化,且须是简单赋值,不能包括复杂的表达式、语句和函数调用。
//静态变量(不可变)staticN:i32=5;//静态变量(可变)staticmutN:i32=5;
常量
const的生命周期也是整个程序,const与static的最大区别在于,编译器并不一定会给const常量分配内存空间,在编译过程中,它很可能会被内联优化,类似于C语言的宏定义。
constN:i32=5;2.3函数
使用fn声明函数。
fnmain(){println!("Hello,world!");}参数需要指定类型
fnprint_sum(a:i8,b:i8){println!("sumis:{}",a+b);}默认返回值为空(),如果有返回值,需要使用-指定返回类型。
fnplus_one(a:i32)-i32{a+1//等价于returna+1,可省略为a+1}可以利用元组(tuple)返回多个值
fnplus_one(a:i32)-(i32,i32){(a,a+1)}fnmain(){let(add_num,result)=plus_one(10);println!("{}+1={}",add_num,result);//10+1=11}函数指针也可以作为变量使用
letb=plus_one;letc=b(5);//62.4基本数据类型
布尔值(bool)
字符(char)
有符号整型(i8,i16,i32,i64,i128)
无符号整型(u8,u16,u32,u64,u128)
指针大小的有符号/无符号整型(isize/usize,取决于计算机架构,32bit的系统上,isize等价于i32)
浮点数(f32,f64)
数组(arrays),由相同类型元素构成,长度固定。
leta=[1,2,3];//a[0]=1,a[1]=2,a[2]=3letmutb=[1,2,3];letc:[int;3]=[1,2,3];//[类型;数组长度]letd:["myvalue";3];//["myvalue","myvalue","myvalue"];lete:[i32;0]=[];//空数组println!("{:?}",a);//[1,2,3]数组(arrays)的长度是可不变的,动态/可变长数组可以使用Vec(非基本数据类型)。
元组(tuples),由相同/不同类型元素构成,长度固定。
leta=(1,1.5,true,'a',"Hello,world!");//=1,=1.5,=true,='a',="Hello,world!"letb:(i32,f64)=(1,1.5);let(c,d)=b;//c=1,d=1.5let(e,_,_,_,f)=a;//e=1,f="Hello,world!",_作为占位符使用,表示忽略该位置的变量letg=(0,);//只包含一个元素的元组leth=(b,(2,4),5);//((1,1.5),(2,4),5)println!("{:?}",a);//(1,1.5,true,'a',"Hello,world!")元组的长度也是不可变的,更新元组内元素的值时,需要与之前的值的类型相同。
切片(slice),指向一段内存的指针。
切片并没有拷贝原有的数组,只是指向原有数组的一个连续部分,行为同数组。访问切片指向的数组/数据结构,可以使用操作符。
leta:[i32;4]=[1,2,3,4];letb:[i32]=a;//全部letc=a[0..4];//[0,4)letd=a[..];//全部lete=a[1..3];//[2,3]lete=a[1..];//[2,3,4]lete=a[..3];//[1,2,3]
字符串(str)
在Rust中,str是不可变的静态分配的一个未知长度的UTF-8字节序列。str是指向该字符串的切片。
leta="Hello,world!";//a:'staticstrletb:str="你好,世界!";
字符串切片str指向的字符串是静态分配的,在Rust中,有另一个堆分配的,可变长的字符串类型String(非基本数据类型)。通常由字符串切片str通过to_string()或String::from()方法转换得到。
lets1="Hello,world!".to_string();lets2=String::from("Hello,world!");函数(functions)
函数指针也是基本数据类型,可以赋值给其他的变量。
2.5操作符算数运算符
+-*/%leta=5;letb=a+1;//6letc=a-1;//4letd=a*2;//10lete=a/2;//⭐️2=a%2;//1letg=5.0/2.0;//2.5
比较运算符
===!===leta=1;letb=2;letc=a==b;//falseletd=a!=b;//truelete=ab;//trueletf=ab;//falseletg=a=a;//trueleth=a=a;//trueleti=truefalse;//trueletj='a''A';//true
逻辑运算符
!||leta=true;letb=false;letc=!a;//falseletd=ab;//falselete=a||b;//true
位运算符
|^leta=1;letb=2;letc=ab;//0(0110-00)letd=a|b;//3(01||10-11)lete=a^b;//3(01!=10-11)letf=ab;//4(左移-'01'+'00'-100)letg=aa;//0(右移-o̶1̶-0)
赋值运算符
letmuta=2;a+=5;//2+5=7a-=2;//7-2=5a*=5;//5*5=25a/=2;//25/2=12%=5;//12%5=2a=2;//1010-10-2a|=5;//010||101-111-7a^=2;//111!=010-101-5a=1;//'101'+'0'-1010-10a=2;//101̶0̶-10-2
类型转换运算符:as
leta=15;letb=(aasf64)/2.0;//7.5
借用(Borrowing)与解引用(Dereference)操作符
Rust引入了所有权(Ownership)的概念,所以在引用(Reference)的基础上衍生了借用(Borrowing)的概念,所有权概念不在这里展开。
简单而言,引用是为已存在变量创建一个别名;获取引用作为函数参数称为借用;解引用是与引用相反的动作,目的是返回引用指向的变量本身。
//解引用:*fnmain(){//获取v的第2个元素的可变引用,并通过解引用修改该元素的值。letv=mut[1,2,3,4,5];{letthird=_mut(2).unwrap();*third+=50;}println!("v={:?}",v);//v=[1,2,53,4,5]}//解引用:*fnmain(){//获取v的第2个元素的可变引用,并通过解引用修改该元素的值。letv=mut[1,2,3,4,5];{letthird=_mut(2).unwrap();*third+=50;}println!("v={:?}",v);//v=[1,2,53,4,5]}2.6控制流(ControlFlows)if-elseif-else
letteam_size=7;ifteam_size5{println!("Small");}elseifteam_size10{println!("Medium");}else{println!("Large");}//条件块中有返回值时,类型需要一致,可替代C语言的三目运算符letis_below_eighteen=ifteam_size18{true}else{false};match
可替代C语言的switchcase。
lettshirt_width=20;lettshirt_size=matchtshirt_width{16="S",//check1617|18="M",//check17and181921="L",//checkfrom19to21(19,20,21)22="XL",_="NotAvailable",};println!("{}",tshirt_size);//L_表示匹配剩下的任意情况。
while
letmuta=1;whilea=10{println!("Currentvalue:{}",a);a+=1;//Rust不支持++/--自增自减语法}loop
类似于C语言的while(1)
letmuta=0;loop{ifa==0{println!("SkipValue:{}",a);a+=1;continue;}elseifa==2{println!("BreakAt:{}",a);break;}println!("CurrentValue:{}",a);a+=1;}//SkipValue:0//CurrentValue:1//BreakAt:2for
forain0..10{//(a=0;a10;a++)println!("Currentvalue:{}",a);}'outer_for:forc1in1..6{//setlabelouter_for'inner_for:forc2in1..6{println!("CurrentValue:[{}][{}]",c1,c2);ifc1==2c2==2{break'outer_for;}//结束外层循环}}letgroup:[str;4]=["Mark","Larry","Bill","Steve"];(){println!("CurrentPerson:{}",person);}在for表达式中的break'outer_for,loop和while也有相同的使用方式。
3.其他数据类型3.1结构体(struct)和元组(tuple)一样,结构体(struct)支持组合不同的数据类型,但不同于元组,结构体需要给每一部分数据命名以标志其含义。因而结构体比元组更加灵活,不依赖顺序来指定或访问实例中的值。
定义结构体
structUser{username:String,email:String,sign_in_count:u64,active:bool,}创建实例
letuser1=User{email:String::from("someone@"),username:String::from("someusername123"),active:true,sign_in_count:1,};修改某个字段的值
letmutuser1=User{email:String::from("someone@"),username:String::from("someusername123"),active:true,sign_in_count:1,};=String::from("anotheremail@");变量与字段名同名的简写语法
structColor(i32,i32,i32);structPoint(i32,i32);letblack=Color(0,0,0);letorigin=Point(3,4);
元组结构体(tuplestructs)
元组结构体有着结构体名称提供的含义,但没有具体的字段名。在参数个数较少时,无字段名称,仅靠下标也有很强的语义时,为每个字段命名就显得多余了。例如:
12345678
VS
structPoint{x:i32y:i32}letorigin=Point{x:3y:4}structPoint{x:i32y:i32}letorigin=Point{x:3y:4}3.2枚举(enum)定义枚举
enumIpAddrKind{V4,V6,}使用枚举值
letfour=IpAddrKind::V4;fnroute(ip_type:IpAddrKind){}route(four);route(IpAddrKind::V6);枚举成员关联数据
enumIpAddr{V4(u8,u8,u8,u8),V6(String),}lethome=IpAddr::V4(127,0,0,1);letloopback=IpAddr::V6(String::from("::1"));更复杂的例子
enumMessage{Quit,//不关联数据Move{x:i32,y:i32},//匿名结构体Write(String),ChangeColor(i32,i32,i32),}match控制流
enumCoin{Penny,Nickel,Dime,Quarter,}fnvalue_in_cents(coin:Coin)-u32{matchcoin{Coin::Penny={println!("Luckypenny!");1},Coin::Nickel=5,Coin::Dime=10,Coin::Quarter=25,}}Option
Option是标准库中定义的一个非常重要的枚举类型。Option类型应用广泛因为它编码了一个非常普遍的场景,即一个值要么有值要么没值。对Rust而言,变量在使用前必须要赋予一个有效的值,所以不存在空值(Null),因此在使用任意类型的变量时,编译器确保它总是有一个有效的值,可以自信使用而无需做任何检查。如果一个值可能为空,需要显示地使用OptionT来表示。
Option的定义如下:
pubenumOptionT{Some(T),None,}OptionT包含2个枚举项:
1)None,表明失败或没有值2)Some(value),元组结构体,封装了一个T类型的值value
得益于Option,Rust不允许一个可能存在空值的值,像一个正常的有效值那样工作,在编译时就能够检查出来。Rust显得更加安全,不用担心出现其他语言运行时才会出现的空指针异常的bug。例如:
letx:i8=5;//Rust没有空值(Null),因此i8只能被赋予一个有效值。lety:Optioni8=Some(5);//y可能为空,需要显示地表示为枚举类型Optionletsum=x+y;
尝试将不可能出现无效值的x:i8与可能出现无效值的y:Optioni8相加时,编译器会报错:
error[E0277]:thetraitbound`i8:std::ops::Addstd::option::Optioni8`isnotsatisfied--|5|letsum=x+y;|^noimplementationfor`i8+std::option::Optioni8`|
总结一下,如果一个值可能为空,必须使用枚举类型OptionT,否则必须赋予有效值。而为了使用OptionT,需要编写处理每个成员的代码,当T为有效值时,才能够从Some(T)中取出T的值来使用,如果T为无效值,可以进行其他的处理,通常使用match来处理这种情况。
例如,当y为有效值时,返回x和y的和;为空值时,返回x。
fnplus(x:i8,y:Optioni8)-i8{matchy{None=x,Some(i)=x+i,}}fnmain(){lety1:Optioni8=Some(5);lety2:Optioni8=None;letz1=plus(10,y1);letz2=plus(10,y2);println!("z1={},z2={}",z1,z2);//z1=15,z2=10}iflet控制流
match还有一种简单场景,可以简写为iflet。如下,y有值时,打印和,y无值时,啥事也不做。
fnplus(x:i8,y:Optioni8){matchy{Some(i)={println!("x+y={}",x+i)},None={},}}fnmain(){lety1:Optioni8=Some(5);lety2:Optioni8=None;plus(10,y1);//x+y=15plus(10,y2);}简写为iflet,则是
fnplus(x:i8,y:Optioni8){ifletSome(i)=y{println!("x+y={}",x+i);}}如果只使用if呢?
fnplus(x:i8,y:Optioni8){_some(){leti=();//获得Some中的T值。println!("x+y={}",x+i);}}iflet语句也可以包含else。
fnplus(x:i8,y:Optioni8){ifletSome(i)=y{println!("x+y={}",x+i);}else{println!("yisNone");}}//等价于fnplus(x:i8,y:Optioni8){matchy{Some(i)={println!("x+y={}",x+i)},None={println!("yisNone")},}}3.3实现方法和接口(impltraits)实现方法(impl)
structRectangle{width:u32,height:u32,}implRectangle{fnarea(self)-u32{*}}implRectangle{fncan_hold(self,other:Rectangle)-bool{}}fnmain(){letrect1=Rectangle{width:30,height:50};println!("Theareaoftherectangleis{}squarepixels.",());}关联函数(associatedfunctions)
关联函数不以self作为参数,关联函数之所以成为函数而不是方法,是因为关联函数并不作用于一个结构体的实例。我们之前创建字符串类型时,使用过的String::from就是关联函数。关联函数经常被用作返回一个结构体新实例的构造函数。例如我们可以提供一个关联函数,它接受一个维度参数并且同时作为宽和高,这样可以更轻松的创建一个正方形Rectangle而不必指定两次同样的值:
implRectangle{fnsquare(size:u32)-Rectangle{Rectangle{width:size,height:size}}}letrect2=Rectangle::square(10);实现接口(traits)
traitSummary{fnsummarize(self)-String;}implSummaryforRectangle{fnsummarize(self)-String{format!("{width={},height={}}",,)}}//接口也支持继承traitPerson{fnfull_name(self)-String;}traitEmployee:Person{//Employeeinheritfrompersontraitfnjob_title(self)-String;}traitExpat{fnsalary(self)-f32}traitExpatEmployee:Employee+Expat{//多继承,同时继承Employee和Expatfnadditional_tax(self)-f64;}3.3泛型(Generics)当我们实现一个函数或者数据结构时,往往希望参数可以支持不同的类型,泛型可以解决这个问题。声明参数类型时,换成大写的字母,例如字母T,同时使用T告诉编译器T是泛型。
函数中使用泛型
fnlargestT(list:[T])-T{letmutlargest=list[0];(){ifitemlargest{largest=item;}}largest}fnmain(){letnumber_list=vec![34,50,25,100,65];letresult=largest(number_list);println!("Thelargestnumberis{}",result);letchar_list=vec!['y','m','a','q'];letresult=largest(char_list);println!("Thelargestcharis{}",result);}结构体使用泛型
structPointT{x:T,y:T,}fnmain(){letinteger=Point{x:5,y:10};letfloat=Point{x:1.0,y:4.0};}枚举使用泛型
enumOptionT{Some(T),None,}enumResultT,E{Ok(T),Err(E),}Result枚举有两个泛型类型,T和E。Result有两个成员:Ok,它存放一个类型T的值,而Err则存放一个类型E的值。这个定义使得Result枚举能很方便的表达任何可能成功(返回T类型的值)也可能失败(返回E类型的值)的操作。回忆一下示例9-3中打开一个文件的场景:当文件被成功打开T被放入了std::fs::File类型而当打开文件出现问题时E被放入了std::io::Error类型。
方法中使用泛型
structPointT{x:T,y:T,}implTPointT{fnx(self)-T{}}fnmain(){letp=Point{x:5,y:10};println!("={}",());}3.4常见集合Vec新建
letv:Veci32=Vec::new();//空集合//letv=vec![1,2,3];//含初始值的集合,vec!是为方便初始化Vec提供的宏。println!("第三个元素{}",v[2]);//3println!("第100个元素{}",v[100]);//panicerrorassert_eq!((2),Some(3));assert_eq!((100),None);(2)和v[2]都能获取到Vec的值,区别在于v[2]返回的是该元素的引用,引用一个不存在的位置,会引发错误。(2)返回的是枚举类型OptionT。(2)返回的是Some(3),(100)返回的是None。
更新
letv:Veci32=Vec::new();(5);(6);(7);(8);()//删除最后一个元素
遍历
letv=vec![100,32,57];foriinv{println!("{}",i);}letmutv2=vec![100,32,57];foriinmutv2{*i+=50;}iflet控制流
如果我们想修改Vec中第2个元素的值呢?可以这么写:
fnmain(){letmutv=vec![1,2,3,4,5];{letthird=_mut(2).unwrap();*third+=50;}println!("v={:?}",v);//v=[1,2,53,4,5]}因为_mut()的返回值是OptionT枚举类型,那么可以使用iflet来简化代码。
fnmain(){letmutv=vec![1,2,3,4,5];ifletSome(third)=_mut(2){*third+=50;}println!("v={:?}",v);//v=[1,2,53,4,5]}whilelet控制流
iflet可以用于单个元素的场景,whilelet就适用于遍历的场景了。
letmutstack=vec![1,2,3,4,5];whileletSome(top)=(){println!("{}",top);//依次打印54321}3.5常见集合StringRust的核心语言中只有一种字符串类型:str,字符串切片,它通常以被借用的形式出现,str。这里提到的字符串,是字节的集合,外加一些常用方法实现。因为是集合,增持增删改,长度也可变。
新建
letmuts1=String::new();lets2="initialcontents".to_string();lets3=String::new();
更新
letmuts=String::from("foo");_str("bar");//附加字符串('!')//附加单字符assert_eq!((0),'f');//删除某个位置的字符lets1=String::from("Hello,");lets2=String::from("world!");lets3=s1+s2;format
lets1=String::from("tic");lets2=String::from("tac");lets3=String::from("toe");lets=format!("{}-{}-{}",s1,s2,s3);println!("{}",s);//tic-tac-toe索引
letv=String::from("hello");assert_eq!(Some('h'),().nth(0));遍历
letv=String::from("hello");(){println!("{}",c);}在Rust内部,String是一个Vecu8的封装,但是有些字符可能会占用超过2个字符,所以String不支持直接索引,如果需要索引需要使用chars()转换后再使用。
3.6常见集合HashMap新建
usestd::collections::HashMap;letmutscores=HashMap::new();(String::from("Blue"),10);(String::from("Yellow"),50);这里使用了use引入了HashMap结构体。
访问
usestd::collections::HashMap;letmutscores=HashMap::new();(String::from("Blue"),10);(String::from("Yellow"),50);letteam_name=String::from("Blue");letscore=(team_name);更新
usestd::collections::HashMap;letmutscores=HashMap::new();(String::from("Blue"),10);//10(String::from("Blue"),25);//25//Blue存在则不更新,不存在则更新,因此scores['Blue']仍为25(String::from("Blue")).or_insert(50);4错误处理4.1不可恢复错误panic!Rust有panic!宏。当执行这个宏时,程序会打印出一个错误信息,展开并清理栈数据,然后接着退出。出现这种场景,一般是出现了一些不知如何处理的场景。
直接调用
fnmain(){panic!("crashandburn");}执行cargorun将打印出
$(/xxx/demo/tutu)Finisheddev[unoptimized+debuginfo]target(s)`target/debug/tutu`thread'main'panickedat'crashandburn',src/:2:5note:runwith`RUST_BACKTRACE=1`environmentvariabletodisplayabacktrace.
最后2行包含了panic!导致的报错信息,第1行是源码中panic!出现的位置src/:2:5
代码bug引起的错误
fnmain(){letv=vec![1,2,3];v[99];//越界}和之前一样,cargorun的报错信息只有2行,缺少函数的调用栈,为了便于定位问题,可以设置RUST_BACKTRACE环境变量来获得更多的调用栈信息,Rust中称之为backtrace。通过backtrace,可以看到执行到目前位置所有被调用的函数的列表。
例如执行RUST_BACKTRACE=1cargorun,这种方式的好处在于,环境变量只作用于当前命令。
$RUST_BACKTRACE=1cargorunFinisheddev[unoptimized+debuginfo]target(s)`target/debug/tutu`thread'main'panickedat'indexoutofbounds:thelenis3buttheindexis99',/rustc/xxx/src/libcore/slice/:2717:10stackbacktrace:0:backtrace::backtrace::libunwind::traceat/cargo/registry/src///src/backtrace/:8817:alloc::vec::VecTascore::ops::index::IndexI::indexat/rustc/xxx/src/liballoc/:179618:tutu::mainatsrc/:4note:Somedetailsareomitted,runwith`RUST_BACKTRACE=full`foraverbosebacktrace.
第一行的报错信息,说明了错误的原因,长度越界。紧接着打印出了函数调用栈,src/:4-liballoc/:1796-…
在windows下,可以执行setRUST_BACKTRACE=1cargorun。
release
当出现panic时,程序默认会开始展开(unwinding),这意味着Rust会回溯栈并清理它遇到的每一个函数的数据,不过这个回溯并清理的过程有很多工作。另一种选择是直接终止(abort),这会不清理数据就退出程序。那么程序所使用的内存需要由操作系统来清理。
release模式下,希望程序越小越好,可以在中设置panic为abort。
[]panic="abort"4.2可恢复错误Result
处理Result
有些错误,希望能够捕获并且做相应的处理,Rust提供了Result机制来处理可恢复错误,类似于其他语言中的trycatch。
这是ResultT,E的定义
enumResultT,E{Ok(T),Err(E),}有些函数会返回Result,那怎么知道一个函数的返回对象是不是Result呢?很简单!
fnmain(){letf:u32=File::create("");}当我们编译上面的代码时,将会报错。
=note:expectedtype`u32`foundtype`std::result::Resultstd::fs::File,std::io::Error`
从报错信息可以看出,File::create返回的是一个Resultfs::::Error对象,如果没有异常,我们可以从Result::OkT获取到文件句柄。
下面是一个完整的示例,创建文件,并尝试写入“Hello,world!”。
usestd::fs::File;usestd::io::prelude::*;fnmain(){letf=File::create("");letmutfile=matchf{Ok(file)=file,Err(error)={panic!("Problemcreatethefile:{:?}",error)},};_all(b"Hello,world!"){Ok(())={},Err(error)={panic!("Failedtowrite:{:?}",error)}};}如果执行成功,可以看到在工程根目录下,多出了文件。
unwrap和expect
Result的处理有时太过于繁琐,Rust提供了一种简洁的处理方式unwrap。即,如果成功,直接返回Result::OkT中的值,如果失败,则直接调用!panic,程序结束。
letf=File::open("").unwrap();//若成功,f则被赋值为文件句柄,失败则结束。expect是更人性化的处理方式,允许在调用!panic时,返回自定义的提示信息,对调试很有帮助。
letf=File::open("").expect("");返回Result
我们可以实现类似于File::open这样的函数,让调用者能够自主绝对如何处理成功/失败的场景。
usestd::io;usestd::io::Read;usestd::fs::File;fnread_username_from_file()-ResultString,io::Error{letf=File::open("");letmutf=matchf{Ok(file)=file,Err(e)=returnErr(e),};letmuts=String::new();_to_string(muts){Ok(_)=Ok(s),Err(e)=Err(e),}}上面的函数如果成功,则返回的文本字符串,失败,则返回io::Error。
更简单的实现方式
usestd::io;usestd::io::Read;usestd::fs::File;fnread_username_from_file()-ResultString,io::Error{letmutf=File::open("")?;letmuts=String::new();_to_string(muts)?;Ok(s)}这种写法使用了?运算符向调用者返回错误。
作用是:如果Result的值是Ok,则该表达式返回Ok中的值且程序继续执行。如果值是Error,则将Error的值作为整个函数的返回值,好像使用了return关键字一样。这样写,逻辑更为清晰。
5包、crate和模块5.1包和crate.├──├──├──benches│└──├──examples│└──├──src│├──bin││└──another_│├──│└──└──tests└──
一个Cargo项目即一个包(Package),一个包至少包含一个crate;可以包含零个或多个二进制crate(binarycrate),但只能包含一个库crate(librarycrate)。src/是与包名同名的二进制crate的根,其他的二进制crate的根放置在src/bin目录下;src/是与包名同名的库crate的根。
5.2模块模块让我们可以将一个crate中的代码进行分组,以提高可读性与重用性。即项是可以被外部代码使用的(public),还是作为一个内部实现的内容,不能被外部代码使用(private)。
声明模块
Rust中使用mod来声明模块,模块允许嵌套,可以使用模块名作为路径使用,例如:
//src/{modbasic{fnplus(x:i32,y:i32)-i32{x+y}fnmul(x:i32,y:i32)-i32{x*y}}}fnmain(){println!("2+3={}",math::basic::plus(2,3));println!("2*3={}",math::basic::mul(2,3));}引入作用域
使用use可以将路径引入作用域。
我们在src/中声明一个模块,在src/中调用。
//src/{pubfnhello(name:str){println!("Hello,{}",name)}//pub才能外部可见}//src/;fnmain(){tutu::greeting::hello("Jack");//Hello,Jack}路径的长度可以自由定义,也可以写成
//src/::greeting;fnmain(){greeting::hello("Jack");}src/和src/属于不同的crate,所以引入作用域时,需要带上包名tutu。
分隔模块
在src/中可以使用mod声明多个模块,但有时为了可读性,习惯将每个模块写在独立的文件中。
新建src/,写入
//src/(name:str){println!("Hello,{}",name)}在src/可以这样使用,
//src/;pubfnfunc(){greeting::hello("Tom");}关键点就在于modgreeting;这一行,modgreeting后面没有具体实现,而是紧跟分号,则声明greeting的模块内容位于src/中。
其他crate,例如src/中的使用方式没有任何变化。
//src/::greeting;fnmain(){greeting::hello("Jack");}6测试6.1单元测试(unittests)fnplus(x:i32,y:i32)-i32{x+y}fnmain(){letx=10;lety=20;println!("{}+{}={}",x,y,plus(x,y))}[test]即可,通过cargotest执行用例。$cargotestrunning1testtestit_worksoktestresult:;0failed;0ignored;0measured;0filteredout
更规范的写法是在每个源文件中,创建包含测试函数的tests模块,测试用例写在tests模块中,并使用cfg(test)标注模块。
fnplus(x:i32,y:i32)-i32{x+y}fnmain(){println!("2+3={}",plus(2,3));}[test]fnit_works(){assert_eq!(4,plus(2,2),);}}因为内部测试的代码和源码在同一文件,因而需要使用[test]fnit_works(){assert_eq!(4,tutu::plus(2,2));}
运行cargotest,将输出
2running1testtestit_adds_twooktestresult:;0failed;0ignored;0measured;0filteredout参考
Rust官方指南
Rust官方文档
Cargo官方文档
推荐阅读
-
细节之中看出个性,时髦星人才会的穿搭术
【导读】时髦其实是一门大学问,编编也常说它是一个技术活,大家在看衣服的时候不要盲目的买买买,也要掌握几招穿搭术,才能从路人甲变身时髦星人。是不是想想有点小激动呢?今天要说的就是只有时髦精才会的穿搭术,一般人我可不告诉她!【穿搭术一:塞上衣】乍一听“塞上衣”,感觉这简直弱爆了,可是把上衣下摆塞进裤子里...
-
咔兹一声爆汁!市区步行街旁的这家新开生煎包,即将引发排队狂潮
注意!注意!注意!下面插播一则特别提示请在进行“这件事”的时候确保身边没人,或使用正确的进食方式否则…否则身边的Ta将“遭殃”↓↓↓上面这个场景,是不是让你想起了星爷《食神》里的爆浆牛肉丸?◆◆◆◆这个包子,有将近200年历史地道生煎老上海◆◆◆◆它起源于19世纪20年代的老上海除了见证上海这座国际...
-
新手开洗衣店必须要注意的事
如果你想开一家干洗加盟店,如果你对干洗加盟行业感兴趣,那么你就需要了解干洗这一行的趋势。干洗加盟市场随着火爆的市场行情,市场容量在不断的扩大,而其中的战火在不断蔓延,干洗加盟行业也在发生着一定的变化。现如今,干洗市场的主营业务内容也慢慢扩大,涵盖了衣物,鞋类、奢侈品、车品、家居用品等涉及到生活的方方...
-
江南园林:一座中国文化博物馆
江南园林绝非单纯的表面形式美,形式背后有着极其丰富的内容,它是形式与内容的高度统一,内容不一样,形式随之变化。以苏州、扬州为例,历史上扬州因大运河漕运和盐运中转地位,富商云集,清代乾隆年间造园数量一度超过苏州,李斗的《扬州画舫录》称“杭州以湖山胜,苏州以市肆胜,扬州以园亭胜”。这时扬州园林主人以盐商...