指令
- rustc -V: 版本
- argo new demo01:创建项目:
- cargo check:检查语法
- cargo run:运行
- cargo build :构建
变量
- 可变性
- 常量
- 隐藏
const MAX_POINT:u32=10000;
fn main() {
//变量定义
let a=1;//默认类型可推导
println!("a={}",a);
// let b:u32=10;
// b=30; //cannot assign twice to immutable variable:不能对不可变变量赋值两次
//rust默认let定义变量,是不可变的,除非添加mut
let mut b:u32=100;
println!("b={}",b);
b=30;
println!("b={}",b);
//隐藏性:后面变量会覆盖前面同名的变量,就叫隐藏性;之后代码使用的b就是浮点型的了
let b:f32 =1.1;
println!("b={}",b); //b=1.1
//常量
println!("MAX_POINT={}",MAX_POINT); //MAX_POINT=10000
}
数据类型
基础数据类型
- bool
- char 32位
- 数字类型
- 数组 [type;size]
- 自适应类型
fn main() {
//bool
let a=true;
let b:bool = false;
println!("a={}",a);
println!("a={},{}",a,b);//a=true,false 其实打印时候只和{}一一对应
//char:rust中char是三十二位的,所以可以是汉字
let c:char ='a';
println!("c={}",c);//c=a
//数字:i8 i16 i32 i64 ,u8 u16 u32 u64 ;前面是有符号的,后面是无符号的
//f32 f64 浮点型
let d:u8=10;
println!("d={}",d);
//自适应类型:isize(有符号) usize(无符号);会根据不同平台自适应,例如:16位平台,32位平台用usize这种就会不同
println!("max={}",usize::max_value()); //max=18446744073709551615 usize的最大范围
//数组:[type;size] size也是数组类型的一部分
let arr:[u32;5] =[1,2,3,4,5];//长度5的u32数组
println!("arr[0]={}",arr[0]);//arr[0]=1
// show(arr);//expected an array with a fixed size of 3 elements, found one with 5 elements
//从上可知,size也是数组类型的一部分
}
fn show(arr:[u32;3]){
for i in &arr{
println!("{}",i);
}
}
复合数据类型
- 元组
- 结构体
- 枚举
fn main() {
//元组
let tup:(i32,f32,char) =(-3,3.68,'哈');
println!("{}-{}-{}",tup.0,tup.1,tup.2); //-3-3.68-哈
//结构体
//枚举
}
函数
fn other_fun(a:i32,b:u32){
// fn other_fun(){ //error 这个时候没办法自动推导,会报错
println!("a={},b={}",a,b);
}
fn other_fun1(a:i32,b:i32) -> i32 {
//如果此处a,b类型不同则不能放一起相加
return a+b;
}
fn other_fun2(a:i32,b:i32) -> i32 {
// let r=a+b;
//注意:下面三种返回方式等效,后面两种都没有分号
// return r;
// r
a+b
}
fn main() {
let a:i32 =-1;
let b:u32 =20;
other_fun(a,b);
let c:i32=10;
let r:i32 = other_fun1(a,c); //直接打印该行不可行,因为不知道类型,所以需要变量接收
println!("r={}",r);//r=9
let r1:i32=other_fun2(a,c);
println!("r1={}",r1);
//语句是执行一些操作,但是不返回值的指令
// let y=1; //语句,不返回值
// 表达式会计算一些值
let y={
let x=1;
// x+1; //加上分号就是语句啦,但是语句是不返回值的指令的;直接打印y,反而会报错
x+1 //没加分号相当于计算一个值返回
};
println!("y={}",y); //y=2
}
控制流
fn main() {
//if
let y=1;
if y==1 {
println!("y={}",y);
}
//if-else
if y==1 {
println!("y={}",y);
}else{
println!("y={}",y);
}
//if-else if-else
if y==1 {
}else if y==2{
}else{
}
//let中使用if
let a=true;
let x=if a{
5
}else{
6 //注意:内部的返回必须在不同条件都一致,否则rust无法推导具体类型
};
println!("x={}",x);//x=5
//循环十次跳出
let mut counter=0;
loop{
println!("in loop");
if counter==10 {
break;
}
counter+=1;
}
let mut counter1=0;
let res= loop{
if counter1==5 {
break counter1;
}
counter1+=1;
};
//这种就是接受loop的返回值的方式
println!("res={}",res);//res=5
//while循环
let mut i=0;
while i!=10{
i+=1;
};
println!("i={}",i); //i=10
//for循环
let arr:[u32;5]=[1,2,3,4,5];
for i in arr.iter(){
println!("i={}",i);
}
//方式二:使用引用,和上面等效
for i in &arr{
println!("i={}",i);
}
}
所有权
-
String的内存模型
// 1. rust通过所有权机制来管理内存,编译器在编译期就会根据所有权规则对内存的使用进行检查
// 2. 堆和栈
//存储在栈上的需要大小固定;存储在堆上的可变;这是在编译期就确定的,所以可以通过这个方式判断具体变量是在哪里
// 3. 作用域:{} 作用域就是定以所在的花括号范围内,常规理解即可
// 4. String内存回收
// 5. 移动
// 6. clone
// 7. 栈上数据拷贝
// 8. 函数和作用域
fn main() {
{
//s1定以在堆上面的,string类型在编译期编译器是不知道它大小的,因为其可能还有追加
let mut s1=String::from("hello");
s1.push_str("a");
println!("s1={}",s1); //s1=helloa
//String类型离开作用域的时候会调用drop方法,类似于c++的析构函数
}
{
let s1=String::from("hello");
let s2=s1;
println!("s2={}",s2); //s1=hello
// println!("s1={}",s1); //value borrowed here after move :被借用之后使用是会报错的
//因为s1和s2实际上指向同一份内存地址,只是浅拷贝;但是在rust中离开作用域字符串会调用drop
//此时会调用两次,明显是不合理的;所以rust语言做了处理,在s2=s1之后,s1就被干掉了等于;同理也不能使用了
//clone
let s3=s2.clone();//深拷贝,解决上面的问题
println!("s3={}",s3); //s3=hello
}
{
//栈上数据拷贝:copy trait
//只要实现了copy的trait特性,则即使赋值之后也是可以继续使用的,而不会移除;后面再细研究
let a=1;
let b=a;//栈上面的数据实际上就是直接拷贝,没有指针
println!("a={},b={}",a,b);//a=1,b=1
//常用的具有copy trait有:
//所有的整型
//浮点型
//布尔值
//字符类型
//元组
}
let s=String::from("hello world");
takes_ownership(s);
//s传递进入,然后takes_ownership内部有{}会drop;rust会干掉当面作用域的s,所以下面的打印报错;但是例如整型就不会,因为是深拷贝
//如果需要继续使用s,则可以在函数内部直接返回,然后这边接收即可
// println!("{}",s); //value borrowed here after move
}
fn takes_ownership(s:String){
println!("{}",s);
}
引用和借用
fn gives_ownership() ->String {
let s=String::from("hello");
s
}
fn takes_and_gives_back(s:String) -> String {
s
}
fn main() {
let s1=gives_ownership();
println!("{}", s1);//此处可用,因为gives_ownership内部的s通过返回值把所有权转移给了s1
let s2=String::from("hello");
let s3=takes_and_gives_back(s2);
// println!("{}",s2);//此处报错,因为s2所有权已经转交给s3了
println!("s3={}",s3);
let mut s4=String::from("hello");
//引用 &:
let a=calcute_length(&s4);
println!("长度是={}",a); //长度是=5
println!("还可以继续使用s4={}",s4);//重点
//借用:&mut
modify_str(&mut s4);
println!("s4={}",s4); //s4=helloa
test();
//此处就是悬垂引用:报错
let r= test1();
}
//引用:创建一个指向值得引用,但是并不是拥有它,因为不拥有这个值;所以当引用离开其值指向得作用域后也不会被丢弃
fn calcute_length(s:&String) -> usize {
s.len()
}
//借用:引用只是指向不是拥有,所以需要操作得时候,需要使用借用
fn modify_str(s:&mut String){
s.push_str("a");
}
fn test(){
//如下代码是有错得:
//因为,两个引用r1 r2,最后打印实际上按照常规理解是输出两个hello
//但是实际上因为r3=&mut s1; 是借用,可能改变s1的内容
//rust为了避免该种情况发生,会报错
let mut s1=String::from("hello");
let r1=&s1;
let r2=&s1;
println!("{},{}",r1,r2); //此处使用是没有问题的
let r3=&mut s1;
r3.push_str(",world");
//也就是说,r3确实操作能修改s1,但是如果后面再使用了r1,r2;则rust会报错
// println!("{},{}",r1,r2); //immutable borrow later used here
}
fn test1()->&String{
//悬垂引用:类似于野指针,因为s在当前{}之外就会回收了,&s只是引用,不是拥有,返回其实就是野指针了
let s=String::from("hello");
&s
}
slice
//1. 字符串slice是String中一部分值得引用
//2. 字面值就是slice
//3. 其他类型slice
fn main() {
let s=String::from("hello world");
let h=&s[0..5];
// let h=&s[0..=5];//等效于上面
// let h=&s[..=5]; //不写表示从头
// let h=&s[..5];
println!("h={}",h);//h=hello
//还可以
// let h=&s[6..]; //6到结尾
// let h=&s[..]; //直接等于s
//字面值slice
let s1="hh";//是不可变引用类型:&str
//其他类型得slice
let a=[1,2,3,4];
let s2=&a[1..3];
println!("s2[0]={}",s2[0]); //s2[0]=2
}
结构体
fn main(){
//1. 定义结构体
#[derive(Debug)]
struct User {
name: String,
cunt: String,
nonce: u64,
active: bool,
};
//2.创建结构体实例
let xiaoming =User{
name: String::from("xiaoming"),
cunt: String::from("80001000"),
nonce: 1000,
active: true,
};
println!("xiaoming={:?}", xiaoming); //xiaoming=User { name: "xiaoming", cunt: "80001000", nonce: 1000, active: true }
//3. 修改结构体字段
let mut xiaohuang =User{
name: String::from("xiaohuang"),
cunt: String::from("252352352"),
nonce: 10020,
active: true,
};
xiaohuang.nonce=20000;
//4. 参数名字和字段名字
let name = String::from("xiaoxiao");
let cunt=String::from("80935235");
let nonce=20000;
let active=false;
let u1=User{
name
cunt
nonce
active
}
//5. 从其它结构体创建实例
let u2=User{
name:String::from("u2"),//这样name就使用了自己定义得
...u1
};
let u3=User{
...u1
};
//下面是比较死板得方式,推荐上面得
let u4=User{
name:u1.name
};
//6. 元组结构体
// (1) 字段没有名字
// (2)圆括号
struct Point(i32,i32);
let a=Point(10,20);
let b=Point(30,11);
println!("a.x={},a.y={}",a.0,a.1);//此处注意
//7. 没有任何字段得类单元结构体
struct A{}
//8. 打印结构体
// #[derive(Debug)]
//对应结构体上面加一个这个,代表自动推导,就可以直接打印结构体,而不是一个个属性得来了
//注意此处是{:?}
// println!("xiaoming={:?}", xiaoming); //xiaoming=User { name: "xiaoming", cunt: "80001000", nonce: 1000, active: true }
// println!("xiaoming={:#?}", xiaoming);//这种方式会换行打印,更易于观看
}
方法
#[derive(Debug)]
struct Dog{
name:String,
weight:f32,
height:f32,
}
impl Dog{
fn get_name(&self) ->&str {
// &(self.name[..])
//下面方式也可以
&self.name
}
fn get_weight(&self) ->f32{
self.weight
}
// fn get_height(&self) ->f32{
// self.height
// }
fn show(){
println!("jiao")
}
}
impl Dog{
//可以有多个实现,所以方法也可以分开写
fn get_height(&self) ->f32{
self.height
}
}
fn main() {
let d=Dog{
name: String::from("wangcai"),
height:3000.6,
weight:20.1,
};
println!("dog={:#?}",d);
/*
dog=Dog {
name: "wangcai",
weight: 20.1,
height: 3000.6,
}
*/
println!("name={}",d.get_name()); //name=wangcai
println!("weight={}",d.get_weight());//weight=20.1
println!("height={}",d.get_height());//height=3000.6
Dog::show(); //jiao 调用方法
}
枚举类型与匹配
//结构体在函数内部则最后需要分号,因为是语句,但是定义在外面得时候不需要
//1.类似于c语言得方式定义
enum IpAddKind{
V4,
V6,
}
//2.rust语言提倡得方式定义
enum IpAddr2{
V4(String),
V6(String),
}
//3. 可以是不同类型
enum IpAddr3{
V4(u8,u8,u8,u8), //元组
V6(String),
}
//4. 经典用法
enum Message {
Quit,
Move{x:i32, y:i32},//结构体
Write(String),
Change(i32,i32,i32),
}
//等同于
/* struct QuitMessage;//类单元结构体
struct MoveMessage{
x:i32,
y:i32,
}
struct WriteMessage(String)
struct ChangeMessage(i32,i32,i32)*/
//5. 枚举类型得方法以及match
impl Message{
//match 类似于c中得switch
fn prin(&self){
match *self{
Message::Quit=>println!("Quit"),
Message::Move{x,y}=> println!("Move x={},y={}",x,y),
Message::Change(a,b,c)=> println!("Change a={},b={},c={}",a,b,c),
_=> println!("default"),//_类似于default
//结构体里面是字符串,结果发现传递进去是引用,所以不行会报错,一般用上面得_形式去打印
// Message::Write(&s)=> println!("Write={}",s),
}
}
}
fn main() {
struct IpAddr{
kind: IpAddKind,
address:String,
};
let i1=IpAddr{kind: IpAddKind::V4, address:String::from("127.0.0.1")};
let i1=IpAddr2::V4(String::from("127.0.0.1"));
let i1=IpAddr3::V4(127,0,0,1);
let i2=IpAddr3::V6(String::from("127.0.0.1"));
//枚举类型得方法以及match
let quit=Message::Quit;
quit.prin();
let mo=Message::Move{x:127,y:0};
mo.prin();
}
Option
//1. Option是标准库定义得一个枚举,形式:
// enum Option<T> {
// Some(T),
// None,
// }
//2.使用方式
fn main() {
let a1=Some(1);
let a2=Some(String::from("a string"));
let a3:Option<i32> =None;
let x:i32 =5;
let y:Option<i32> =Some(5);
let mut temp=0;
//注意此处,如果直接x+y是不行得,因为i32和Option<i32>是两种类型,因为Option中可能存在none,所以需要匹配处理
match y{
Some(i) =>{temp=i;}
None=>println!("do nothing")
}
let sum=x+temp;
println!("sum={}",sum); //sum=10
let result=plus_one(y);
match result{
Some(i)=>println!("result={}",i), //result=6
None=>println!("nothing"),
}
//但是如果不想匹配none呢?可以使用下面的方式
if let Some(value) =plus_one(y){
println!("value={}",value); //value=6
}else{
//此处处理none得情况,但是实际上可以省略得
println!("do nothing");
}
}
fn plus_one(x:Option<i32>)->Option<i32>{
match x{
None =>None, //match必须把Option得所有类型都匹配完,否则编译报错
Some(x) =>Some(x+1),
}
}
Vector
//1. 创建空得vector: Vec<T>
//2. 创建包含初始值得vector
//3. 丢弃vector
//4. 读取元素
//5. 更新
//6. 遍历
//7. 使用枚举
fn main() {
//1. 空Vector,但是如果空的,因为不可变性,所以其实后续操作无意义,一般都加上mut
// let mut v:Vec<i32> = Vec::newnew();
// v.push(1);
//2
let v=vec![1,2,3];//带有初始值
//3
{
let v=vec![1,2,3];
//花括号之外就会被丢弃
}
//4
let one:&i32=&v[0];
println!("one={}", *one); //one=1
// println!("one={}", one);//同上,实际上只是自动划换,本质还是加上*
//推荐
match v.get(1){
Some(v) => println!("v={}", v),//v=2
_=> println!("do nothing"), //越界会到这里,不会报错,但是上面&v[index];如果越界则会报错
}
//5
let mut v2:Vec<i32>=Vec::new();
v2.push(1);//添加
v2.push(11);
//6
//(1)不可变得遍历
for i in &v2{
println!("i={}", i);
}
//(2)可变得遍历
for i in &mut v2{
//遍历过程中修改原来得数据
*i+=1;
println!("i={}",i);
}
//7:可以把很多类型放在一个vec中,后续工作中可能会用到
enum Context{
Text(String),
Float(f32),
};
let c= vec![
Context::Text(String::from("string")),
Context::Float(0.01),
];
//8. 补充
let mut v=vec![1,2,3,4,5];
let first=&v[0];//此处是不可变引用
v.push(6);//此处相当于可变引用
// println!("first={}",first);//所以此处使用会直接报错
}
字符串
fn main() {
//1. 创建一个空String
let mut s0=String::new();//如果不加mut,因为不可变性,则无任何意义
//2. 通过字面值创建一个String
let s1=String::from("safsaf");
let s1="safsaf".to_string();//等效于上面String::from
println!("s1={}", s1);
//2.1 使用String::from()
//2.2 使用str方式
//3. 更新String
//3.1 push_str
s0.push_str("sfa");
println!("s0={}", s0);//s0=sfa
//3.2 push:只能添加一个字符,用单引号
s0.push('a');
// s0.push_str("sfa");
//3.3 使用+合并字符串
let s1="hello".to_string();
let s2=String::from(" ,world");
// let s3=s1+s2;//这样会报错
let s3=s1+&s2;
println!("s3={}", s3); //s3=hello ,world
// println!("s1={}", s1); //报错,s1+&s2相当于把s1所有权给了s3,所以之后使用s1,报错;但是s2还可以使用,因为添加的是引用
//3.4 使用format
let s431=String::from("tic");
let s432=String::from("tac");
let s433=String::from("toe");
let s434=format!("{}-{}-{}",s431,s432,s433);
println!("s434={}", s434); //s434=tic-tac-toe;注意:之后s431,s432,s433还都可以正常使用
//4. String索引
let s4=String::from("hello");
// let s41=s4[0]; //报错,String类型不能被索引
// 因为rust中字符是UTF-8编码,所以可能出现汉字,肉眼的0其实不是0,所以rust就避免了这种情况出现,直接报错
//5. str索引:这种方式可以,slice方式
let hello="你好1";//slice字符串
let h5=&hello[0..3];//但是如果是到2,则报错,因为2不是边界,例如汉字三个字符
println!("h5={}",h5); //h5=你
//6. 遍历
//6.1 chars
for c in hello.chars() {
println!("c={}",c); //分别打印 c=你 c=好; c=1 会自动转字符打印,根本中英文
}
println!("--------------");
//6.2 bytes
for c in hello.bytes() {
println!("c={}",c);
}
//输出如下
// c=228
// c=189
// c=160
// c=229
// c=165
// c=189
// c=49
}
HashMap类型
use std::collections::HashMap;//导入库,内置的
fn main() {
//1. 创建HashMap<K,V>,
let mut scores:HashMap<String,i32> = HashMap::new();
scores.insert(String::from("Blue"),10);
scores.insert(String::from("Red"),20);
//2. 创建HashMap
let keys =vec![String::from("Blue"),String::from("Red")];
let values =vec![10,20];
//组合成hashmap
let scores:HashMap<_,_> =keys.iter().zip(values.iter()).collect();
//3. 读取
let k=String::from("Blue");
if let Some(v)=scores.get(&k){
//返回的是option,所以这样打印,不能直接打印会报错
//也可以用Match方式
println!("v={}",v); //v=10
}
//4. 遍历
for(key,value) in &scores{
//但是注意:遍历是无序的
println!("{},{}",key,value); //Blue,10 以及Red,20
}
//5. 更新
//插入
let mut ss=HashMap::new();
ss.insert(String::from("Yellow"),30);
ss.insert(String::from("Red"),22);
println!("{:?}",ss); //{"Yellow": 30, "Red": 22}
//键不存在才插入
let mut ss1=HashMap::new();
ss1.insert(String::from("Yellow"),30);
ss1.insert(String::from("Red"),22);
ss1.entry(String::from("Yellow")).or_insert(3);//不存在该键才插入3
println!("{:?}",ss1); //{"Yellow": 30, "Red": 22}
//根据旧值更新一个值
let text="hello world wonderful world";
let mut map=HashMap::new();
//split_whitespace是按照空格分割
for word in text.split_whitespace(){
let count=map.entry(word).or_insert(0);
*count+=1;
}
//统计单词出现次数
println!("map={:?}",map); //map={"hello": 1, "wonderful": 1, "world": 2}
}
模块
一旦涉及模块,则必须通过
cargo run
运行项目
- 案例一:同一个文件中的模块
mod factory{
pub mod produce_r1{
pub fn produce_r11(){
println!("公有的才能在外部被调用");
}
}
mod produce_r2{
fn produce_r22(){
println!("rust默认模块内部都是私有的");
}
}
}
fn main() {
factory::produce_r1::produce_r11();//公有的才能在外部被调用
}
引用自定义模块
自定义模块一般也在项目内部的创建
进入项目文件夹,执行下面指令
`cargo new --lib mylib`
factory.rs本身是没有的,后期创建的,默认只有lib.rs
//lib.rs
//模块声明为pub-相当于声明了factory.rs为模块且是pub,这是必须的,否则外部根本引用不了
pub mod factory;
//以下是自动生成的
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}
- factory.rs
pub mod produce_r1{
pub fn produce_r11(){
println!("公有的才能在外部被调用");
}
}
mod produce_r2{
fn produce_r22(){
println!("rust默认模块内部都是私有的");
}
}
- 项目的Cargo.toml
[package]
name = "demo01"
version = "0.1.0"
authors = ["zengqiang"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
mylib = {path='./mylib'} # 这就是添加自定义lib引用,此处是相对路径,可变化的
- 示例
//外部导入lib
use mylib::factory::produce_r1;
// use mylib::factory::*;//导入该文件所有,而不是具体mod
use mylib::factory::produce_r1 as A;//别名
//其实还可以到下一级,但是不方便导入的服用,类比import
//方便观看,直接在该文件定义mod,说明在mod中使用结构体的问题
mod modA{
#[derive(Debug)]
pub struct A {
pub num:i32,
name: String,
}
impl A {
pub fn new_a()->A{
A{
num:1,
name: String::from("a"),
}
}
pub fn print_a(&self){
println!("num={},name={}",self.num,self.name);
}
}
pub mod modB{
pub fn print_b(){
println!("B");
}
//C模块想用B模块的函数使用super
pub mod modC{
pub fn print_c(){
super::print_b();
println!("C");
}
}
}
}
use modA::A as A1;
fn main() {
//绝对路径调用
mylib::factory::produce_r1::produce_r11();//公有的才能在外部被调用
//使用use 等效于上面
produce_r1::produce_r11();//公有的才能在外部被调用
A::produce_r11(); //公有的才能在外部被调用
//结构体
let a=modA::A::new_a();
a.print_a();//num=1,name=a
//重要
modA::modB::modC::print_c(); //B C
}
使用外部的库
- 项目的Cargo.toml下添加依赖
[package]
name = "demo01"
version = "0.1.0"
authors = ["zengqiang"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rust-crypto="0.2" # 此处就是
- 更改为国内镜像,加速下载(可省略)
1. 项目根目录下面创建`.cargo`文件夹
2. 文件夹下面创建config文件(无后缀)
- 执行
cargo build
或者直接cargo run
都会自动下载包 - 示例
extern crate crypto;//使用外部的库,不能省略
use crypto::digest::Digest;
use crypto::sha3::Sha3;
fn main() {
//使用外部的库:生成hello world的hash值
let mut hasher=Sha3::sha3_256();
hasher.input_str("hello world");
let result=hasher.result_str();
println!("hash={}",result);//hash=644bcc7e564373040999aac89e7622f3ca71fba1d972fd94a31c3bfbf24e3938
}
错误处理(不全面)
use std::io;
use std::io::Read;
use std::fs::File;
fn main() {
let r= read_username_from_file();
// 此处r其实是Result:内置的枚举类型,有Ok和Err两个
match r{
Ok(s) => println!("s={}",s),
Err(e) => println!("e={:?}",e), //e=Os { code: 2, kind: NotFound, message: "No such file or directory" }
}
}
//基础方式:最麻烦
// 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")?; //?代表有错误,直接走到io::Error中,否则继续往下走
// let mut s=String::new();
// f.read_to_string(&mut s)?;
// Ok(s)
// }
//更简洁方式
fn read_username_from_file()-> Result<String, io::Error> {
let mut s=String::new();
File::open("hello.txt")?.read_to_string(&mut s)?;
Ok(s)
}