rust入门

目录

〇,版本

1,语义化版本

2,发行版本

3,Edition 版次

一,输入输出

1,输出

2,输入

二,函数

1,main函数

2,普通函数

3,调用外部函数

4,lambda表达式

三,标识符、变量

1,标识符

2,变量绑定、let、mut

3,变量作用域

4,关键字

5,生命周期

(1)生命周期推导

(2)生命周期标注符

(3)含引用的结构体

(4)静态生命周期

(5)生命周期子类型

(6)单入参函数的生命周期自动推导

(7)带self参数函数的生命周期自动推导

(8)生命周期编译规则总结

四,表达式和流程控制

1,match

(1)按值匹配、按表达式匹配

(2)match语句的类型

(3)通配符

(4)条件匹配

(5)合并分支

五,模块

1,模块结构

六,异步编程

1,async

七,rust内存安全

八,编程规范

1,命名格式

2,常用命名

3,常量类型不应具有内部可变性

4,类型转换

5,数值字面量要添加明确的类型标识

6,宏里面的多条匹配规则,应该按照从小到大排序

7,外部函数边界FFI

九,同名辨析

1,vec、Vec

2,一个类型拥有多个同名函数

3,隐式转换导致拥有多个同名函数

4,trait内的函数和一个单独的函数是同名的

5,标准库的不同包里面的同名数据结构


rust 在线运行

rust入门教程

rust详细文档

〇,版本

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 crateselfsuperSelf

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>;

其中第一个是单元元组,第二个是单元元组结构体。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值