Rust太难?那是你没看到这套Rust语言语言学习总结(下)

举报
技术火炬手 发表于 2020/12/28 18:34:35 2020/12/28
【摘要】 从开发环境、语法、属性、内存管理和Unicode等五部分,为你带来一份详细的Rust语言学习的精华总结内容。

2.6 原生类型

Rust内置的原生类型 (primitive types) 有以下几类:

  • 布尔类型:有两个值truefalse
  • 字符类型:表示单个Unicode字符,存储为4个字节。
  • 数值类型:分为有符号整数 (i8, i16, i32, i64, isize) 无符号整数 (u8, u16, u32, u64, usize) 以及浮点数 (f32, f64)
  • 字符串类型:最底层的是不定长类型str,更常用的是字符串切片&str和堆分配字符串String 其中字符串切片是静态分配的,有固定的大小,并且不可变,而堆分配字符串是可变的。
  • 数组:具有固定大小,并且元素都是同种类型,可表示为[T; N]
  • 切片:引用一个数组的部分数据并且不需要拷贝,可表示为&[T]
  •  元组:具有固定大小的有序列表,每个元素都有自己的类型,通过解构或者索引来获得每个元素的值。
  • 指针:最底层的是裸指针const Tmut T,但解引用它们是不安全的,必须放到unsafe块里。
  • 函数:具有函数类型的变量实质上是一个函数指针。
  • 元类型:即(),其唯一的值也是()

2.7 函数

2.7.1 函数参数

  • 当函数参数按值传递时,会转移所有权或者执行复制(Copy)语义。
  • 当函数参数按引用传递时,所有权不会发生变化,但是需要有生命周期参数(符合规则时不需要显示的标明)。

2.7.2 函数参数模式匹配

  • ref :使用模式匹配来获取参数的不可变引用。
  • ref mut :使用模式匹配来获取参数的可变引用。
  •  除了refref mut,函数参数也可以使用通配符来忽略参数。

具体可见《Rust编程之道》的第165页。

2.7.3 泛型函数

函数参数并未指定具体的类型,而是用了泛型T,对T只有一个Mult trait限定,即只有实现了Mul的类型才可以作为参数,从而保证了类型安全。

泛型函数并未指定具体类型,而是靠编译器来进行自动推断的。如果使用的都是基本原生类型,编译器推断起来比较简单。如果编译器无法自动推断,就需要显式的指定函数调用的类型

2.7.4 方法和函数

方法代表某个实例对象的行为,函数只是一段简单的代码,它可以通过名字来进行调用。方法也是通过名字来进行调用,但它必须关联一个方法接受者

2.7.5 高阶函数

高阶函数是指以函数作为参数或返回值的函数,它是函数式编程语言最基础的特性。

具体可见《Rust编程之道》的第168页。

2.8 闭包Closure

闭包通常是指词法闭包,是一个持有外部环境变量的函数。

外部环境是指闭包定义时所在的词法作用域。

外部环境变量,在函数式编程范式中也被称为自由变量,是指并不是在闭包内定义的变量。

将自由变量和自身绑定的函数就是闭包

闭包的大小在编译期是未知的。

2.8.1 闭包的基本语法

闭包管道符(两个对称的竖线)和花括号(或圆括号)组成。

  • 管道符里是闭包函数的参数,可以向普通函数参数那样在冒号后添加类型标注,也可以省略。例如:let add = |a, b| -> i32 { a + b };
  • 花括号里包含的是闭包函数执行体,花括号和返回值也可以省略。

例如:let add = |a, b| a + b;

  • 当闭包函数没有参数只有捕获的自由变量时,管道符里的参数也可以省略。

例如: let add = || a + b;

2.8.2 闭包的实现

闭包是一种语法糖。闭包不属于Rust语言提供的基本语法要素,而是在基本语法功能之上又提供的一层方便开发者编程的语法。

闭包和普通函数的差别就是闭包可以捕获环境中的自由变量

闭包可以作为函数参数,这一点直接提升了Rust语言的抽象表达能力。当它作为函数参数传递时,可以被用作泛型的trait限定,也可以直接作为trait对象来使用。

闭包无法直接作为函数的返回值,如果要把闭包作为返回值,必须使用trait对象。

2.8.3 闭包与所有权

闭包表达式会由编译器自动翻译为结构体实例,并为其实现FnFnMutFnOnce三个trait中的一个。

  • FnOnce:会转移方法接收者的所有权。没有改变环境的能力,只能调用一次。
  • FnMut:会对方法接收者进行可变借用。有改变环境的能力,可以多次调用。
  • Fn:会对方法接收者进行不可变借用。没有改变环境的能力,可以多次调用。
  •  如果要实现Fn,就必须实现FnMutFnOnce
  •  如果要实现FnMut,就必须实现FnOnce
  • 如果要实现FnOnce,就不需要实现FnMutFn

2.8.3.1 捕获环境变量的方式

  • 对于复制语义类型,以不可变引用(&T来进行捕获。
  • 对于移动语义类型,执行移动语义,转移所有权来进行捕获。
  • 对于可变绑定,并且在闭包中包含对其进行修改的操作,则以可变引用(&mut T来进行捕获。

具体可见《Rust编程之道》的第178页。

Rust使用move关键字来强制让闭包所定义环境中的自由变量转移到闭包中

2.8.3.2 规则总结

  • 如果闭包中没有捕获任何环境变量,则默认自动实现Fn
  •  如果闭包中捕获了复制语义类型的环境变量,则:
  •  如果不需要修改环境变量,无论是否使用move关键字,均会自动实现Fn
  •  如果需要修改环境变量,则自动实现FnMut
  •  如果闭包中捕获了移动语义类型的环境变量,则:
  • 如果不需要修改环境变量,而且没有使用move关键字,则会自动实现FnOnce
  • 如果不需要修改环境变量,而且使用move关键字,则会自动实现Fn
  • 如果需要修改环境变量,则自动实现FnMut

l  FnMut的闭包在使用move关键字时,如果捕获变量是复制语义类型的,则闭包会自动实现Copy/Clone。如果捕获变量是移动语义类型的,则闭包不会自动实现Copy/Clone

2.9 迭代器

         Rust使用的是外部迭代器,也就是for循环。外部迭代器:外部可以控制整个遍历进程。

         Rust中使用了trait来抽象迭代器模式。Iterator traitRust中对迭代器模式的抽象接口。

迭代器主要包含:

  • next方法:迭代其内部元素
  • 关联类型Item
  • size_hint方法:返回类型是一个元组,该元组表示迭代器剩余长度的边界信息。

示例:

         let iterator = iter.into_iter();      

let size_lin = iterator.size_hint(); 

let mut counter = Counter { count: 0};

counter.next();                         

         Iter类型迭代器,next方法返回的是Option<&[T]>Option<&mut [T]>类型的值。for循环会自动调用迭代器的next方法。for循环中的循环变量则是通过模式匹配,从next返回的Option<&[T]>Option<&mut [T]>类型中获取&[T]&mut [T]类型的值。

         Iter类型迭代器在for循环中产生的循环变量为引用

         IntoIter类型的迭代器的next方法返回的是Option<T>类型,在for循环中产生的循环变量是,而不是引用

示例:

let v = vec![1, 2, 3];

for i in v {

    …

}

 

为了确保size_hint方法可以获得迭代器长度的准确信息,Rust引入了两个trait,他们是Iterator的子trait,均被定义在std::iter模块中。

  • ExactSizeIterator :提供了两个额外的方法lenis_empty
  • TrustedLen :像一个标签trait,只要实现了TrustLen的迭代器,其size_hint获取的长度信息均是可信的。完全避免了容器的容量检查,提升了性能。

2.9.1 IntoIterator trait

         如果想要迭代某个集合容器中的元素,必须将其转换为迭代器才可以使用。

         Rust提供了FromIteratorIntoIterator两个trait,他们互为反操作。

  •  FromIterator :可以从迭代器转换为指定类型。
  •  IntoIterator :可以从指定类型转换为迭代器。

Intoiter可以使用into_iter之类的方法来获取一个迭代器。into_iter的参数时self,代表该方法会转移方法接收者的所有权。而还有其他两个迭代器不用转移所有权。具体的如下所示:

  •  Intoiter :转移所有权,对应self
  •  Iter :获取不可变借用,对应&self
  •  IterMut :获得可变借用,对应&mut slef

2.9.2 哪些实现了Iterator的类型?

只有实现了Iterator的类型才能作为迭代器。

实现了IntoIterator的集合容器可以通过into_iter方法来转换为迭代器。

实现了IntoIterator的集合容器有:

  •  Vec<T>
  • &’a [T]
  •  &’a mut [T]  => 没有为[T]类型实现IntoIterator

2.9.3 迭代器适配器

通过适配器模式可以将一个接口转换成所需要的另一个接口。适配器模式能够使得接口不兼容的类型在一起工作。

适配器也叫包装器(Wrapper

迭代器适配器,都定义在std::iter模块中:

  • Map :通过对原始迭代器中的每个元素调用指定闭包来产生一个新的迭代器。
  • lChain :通过连接两个迭代器来创建一个新的迭代器。
  • Cloned :通过拷贝原始迭代器中全部元素来创建新的迭代器。
  • Cycle :创建一个永远循环迭代的迭代器,当迭代完毕后,再返回第一个元素开始迭代。
  • Enumerate :创建一个包含计数的迭代器,它返回一个元组(i,val),其中iusize类型,为迭代的当前索引,val是迭代器返回的值。
  • Filter :创建一个机遇谓词判断式过滤元素的迭代器。
  • FlatMap :创建一个类似Map的结构的迭代器,但是其中不会包含任何嵌套。
  •  FilterMap :相当于FilterMap两个迭代器一次使用后的效果。
  • Fuse :创建一个可以快速遍历的迭代器。在遍历迭代器时,只要返回过一次None,那么之后所有的遍历结果都为None。该迭代器适配器可以用于优化。
  • Rev :创建一个可以反向遍历的迭代器。

具体可见《Rust编程之道》的第202页。

Rust可以自定义迭代器适配器,具体的见《Rust编程之道》的第211页。

2.10 消费器

迭代器不会自动发生遍历行为,需要调用next方法去消费其中的数据。最直接消费迭代器数据的方法就是使用for循环。

Rust提供了for循环之外的用于消费迭代器内数据的方法,叫做消费器(Consumer

Rust标准库std::iter::Iterator中常用的消费器:

l  any :可以查找容器中是否存在满足条件的元素。

l  fold :该方法接收两个参数,第一个为初始值,第二个为带有两个参数的闭包。其中闭包的第一个参数被称为累加器,它会将闭包每次迭代执行的结果进行累计,并最终作为fold方法的返回值。

  • collect :专门用来将迭代器转换为指定的集合类型。
  •  all
  • for_each
  • position

2.11 

  • RwLock读写锁:是多读单写锁,也叫共享独占锁。它允许多个线程读单个线程写。但是在写的时候,只能有一个线程占有写锁;而在读的时候,允许任意线程获取读锁。读锁和写锁不能被同时获取
  • Mutex互斥锁:只允许单个线程读和写。 

三、 Rust属性

  •   #[lang = “drop”] drop标记为语言项
  •   #[derive(Debug)] :
  •   #[derive(Copy, Clone)] :
  •  #[derive(Debug,Copy,Clone)] 
  •   #[lang = “owned_box”]  Box<T>与原生类型不同,并不具备类型名称,它代表所有权唯一的智能指针的特殊性,需要使用lang item来专门识别。
  •  #[lang = “fn/fn_mut/fn_once”] :表示其属于语言项,分别以fnfn_mutfn_once名称来查找这三个trait
  •  fn_once:会转移方法接收者的所有权
  •  fn_mut:会对方法接收者进行可变借用
  •  fn:会对方法接收者进行不可变借用
  •   #[lang = “rust_pareen_sugar”] :表示对括号调用语法的特殊处理。
  •  #[must_use=”iterator adaptors are lazy ……”] :用来发出警告,提示开发者迭代器适配器是惰性的。

四、内存管理

4.1 内存回收

         drop-flag:在函数调用栈中为离开作用域的变量自动插入布尔标记,标注是否调用析构函数,这样,在运行时就可以根据编译期做的标记来调用析构函数。

         实现了Copy的类型,是没有析构函数的。因为实现了Copy的类型会复制,其生命周期不受析构函数的影响。

需要继续深入理解第4章并总结,待后续补充。

五、unicode

Unicode字符集相当于一张表,每个字符对应一个非负整数,该数字称为码点(Code Point

这些码点也分为不同的类型:

  • 标量值
  • 代理对码点
  • 非字符码点
  • l  保留码点
  • l  私有码点

标量值是指实际存在对应字符的码位,其范围是0x0000~0xD7FF0xE000~0x10FFFF两段。

Unicode字符集的每个字符占4个字节,使用的存储方式是:码元(Code Unit组成的序列。

码元是指用于处理和交换编码文本的最小比特组合。

Unicode字符编码表:

  • UTF-8      => 1字节码元
  • UTF-16    => 2字节码元        
  • UTF-32    => 4字节码元

Rust的源码文件.rs的默认文本编码格式是UTF-8

六、Rust附录

字符串对象常用的方法

方法

原型

说明

new()

pub const fn new() -> String

创建一个新的字符串对象

to_string()

fn to_string(&self) -> String

将字符串字面量转换为字符串对象

replace()

pub fn replace<'a, P>(&'a self, from: P, to: &str) -> String

搜索指定模式并替换

as_str()

pub fn as_str(&self) -> &str

将字符串对象转换为字符串字面量

push()

pub fn push(&mut self, ch: char)

再字符串末尾追加字符

push_str()

pub fn push_str(&mut self, string: &str)

再字符串末尾追加字符串

len()

pub fn len(&self) -> usize

返回字符串的字节长度

trim()

pub fn trim(&self) -> &str

去除字符串首尾的空白符

split_whitespace()

pub fn split_whitespace(&self) -> SplitWhitespace

根据空白符分割字符串并返回分割后的迭代器

split()

pub fn split<'a, P>(&'a self, pat: P) -> Split<'a, P>

根据指定模式分割字符串并返回分割后的迭代器。模式 P 可以是字符串字面量或字符或一个返回分割符的闭包

chars()

pub fn chars(&self) -> Chars

返回字符串所有字符组成的迭代器

 

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。