rust中的类型系统(trait的了解)

什么是类型系统?重要性?
这几乎是所有高级语言必须有的组成。计算机存储数据是0,1比特序列。作为开发人员,使用这些比特来存储,处理各种信息心智负担很大,容易出现内存安全问题,没办法有效利用内存。所谓类型,其实就是对表示信息的值进行的细粒度的区分。比如整数、小数、文本等,粒度再细一点,就是布尔值、符号整型值、无符号整型值、单精度浮点数、双精度浮点数、字符和字符串,甚至还有各种自定义的类型。不同的类型占用的内存不同,编码方式不同
还要对这些基本的类型定义一系列的组合、运算、转换等方法来处理信息。
二者组合就是类型系统。
类型系统的作用就是:减小编程心智负担允许开发者在更高层面进行思考比如类的抽象、排查错误保证内存安全。

类型系统的分类
在编译期进行类型检查的语言属于静态类型,在运行期进行类型检查的语言属于动态类型。如果一门语言不允许类型的自动隐式转换,在强制转换前不同类型无法进行计算,则该语言属于强类型,反之则属于弱类型
比如C++就是静态的强类型。有些静态语言,如C和C++,在编译期并不检查数组是否越界访问,运行时可能会得到难以意料的结果,而程序依旧正常运行,这属于类型系统中未定义的行为,所以它们不是类型安全的语言。而Rust语言在编译期就能检查出数组是否越界访问,并给出警告。
rus也是静态强类型的系统
类型系统与多态性
如果一个类型系统允许一段代码在不同的上下文中具有不同的类型,这样的类型系统就叫作多态类型系统。现代编程语言包含了三种多态形式:参数化多态(Parametricpolymorphism)、Ad-hoc多态(Ad-hoc polymorphism)和子类型多态(Subtype polymorphism)

比如C++就支持参数多态(静态多态)和子类型多态(动态多态)。参数多态其实就是泛型编程;运行时多态就是子类指针可以指向派生类对象。虚函数访问派生类行为。
C++多态实现原理:当编译器发现类中有虚函数时,会创建一张虚函数表,把虚函数的函数入口地址放到虚函数表中并且在对象中增加一个指针vptr,用于指向类的虚函数表。当派生类覆盖基类的虚函数时,会将虚函数表中对应的指针进行替换,从而调用派生类中覆盖后的虚函数,从而实现动态绑定。

rust支持参数多态,不支持子类型多态因为没有继承。但是rust支持Ad-hoc多态,也就是重要的trait
Ad-hoc多态也叫特定多态,Ad-hoc多态是指同一种行为定义,在不同的上下文中会响应不同的行为实现

Rust中一切皆表达式,表达式皆有值,值皆有类型。所以可以说,Rust中一切皆类型。
为什么说rust是类型安全的?
除了一些基本的原生类型和复合类型,Rust把作用域也纳入了类型系统,这就是第4章将要学到的生命周期标记。还有一些表达式,有时有返回值,有时没有返回值(也就是只返回单元值),或者有时返回正
确的值,有时返回错误的值,Rust 将这类情况也纳入了类型系统,也就是Option<T>和Result<T,E>这样的可选类型,从而强制开发人员必须分别处理这两种情况。一些根本无法返回值的情况,比如线程崩溃、break或continue等行为,也都被纳入了类型系统,这种类型叫作never类型。可以说,Rust的类型系统基本囊括了编程中会遇到的各种情况一般情况下不会有未定义的行为出现,所以说,Rust是类型安全的语言。只要定义了类型,就能对这些类型进行处理,不会出现未定义的行为。

类型大小
Rust中绝大部分类型都是在编译期可确定大小的类型(SizedType),比如原生整数类型u32固定是4个字节,u64固定是8个字节,等等,都是可以在编译期确定大小的类型。然而,Rust也有少量的动态大
小的类型。
比如str类型的字符串字面量,编译器不可能事先知道程序中会出现什么样的字符串,所以对于编译器来说,str类型的大小是无法确定的。对于这种情况,Rust提供了引用类型,因为引用总会有固定的且在编译期已知的大小。&str就是一种引用类型,它由指针和长度信息组成,这种包含了动态大小类型地址信息和携带了长度信息的指针,叫作胖指针

零大小类型:比如单元类型和单元结构体,大小都是零。这种类型的作用是:介绍过的Rust官方标准
库中的HashSet<T>和BTreeSet<T>。它们其实只是把HashMap<K,T>换成了HashMap<K,()>,然后就可以共用HashMap<K,T>之前的代码,而不需要再重新实现一遍HashSet<T>了。

底类型:它其实是第2章介绍过的never类型。如果说零类型表示“空”的话,那么底类型就表示“无”。底类型无值,而且它可以等价于任意类型。Rust中的底类型用叹号(!)表示。
Rust中有很多种情况确实没有值,但为了类型安全,必须把这些情况纳入类型系统进行统一处理。这些情况包括:
发散函数:比如exit退出不返回值,比如continue,break。

类型推导
自动类型推导。这可以方便编程,有点和python类似。比如sum函数要传入两个int变量,但是可以 let a=1,let b=2,a,b可以直接用。当Rust无法从上下文中自动推导出类型的时候,编译器会通过错误信息提示你,请求你添加类型标注。所以在用Rust编程的时候,应尽量显式声明类型,这样可以避免一些麻烦。

泛型
是一种参数化多态。使用泛型可以编写更为抽象的代码,减少工作量。简单来说,泛型就是把一个泛化的类型作为参数,单个类型就可以抽象化为一簇类型。在第2章中介绍过的Box<T>、Option<T>和Result<T,E>等,都是泛型类型。
除了定义类型,泛型也可以应用于函数中
在这里插入图片描述

正常的函数应该是: fn foo(x:int32) -> int 32{ return x};

深入trait
Rust中所有的抽象,比如接口抽象、OOP范式抽象、函数式范式抽象等,均基于trait来完成。同时,trait也保证了这些抽象几乎都是运行时零开销的。
trait是Rust对Ad hoc多态的支持。从语义上来说,trait是在行为上对类型的约束,这种约束可以让trait有如下4种用法:
· 接口抽象。接口是对类型行为的统一约束。
· 泛型约束。泛型的行为被trait限定在更有限的范围内。
· 抽象类型。在运行时作为一种间接的抽象类型去使用,动态地分发给具体的类型。
· 标签trait。对类型的约束,可以直接作为一种“标签”使用

trait最基础的用法就是进行接口抽象
接口抽象就是同一个接口可以同时被多个类型实现,可以说就是函数重载了。这点在C++也是可以实现的。
Rust中的很多操作符都是基于trait来实现的。比如加法操作符就是一个trait,加法操作不仅可以针对整数、浮点数,也可以针对
字符串。
rust中的类型系统(trait的了解)_第1张图片
泛型约束
使用泛型编程时,很多情况下的行为并不是针对所有类型都实现的。sum 函数中传入的参数是两个整数,那么加法行为是合法的。如果传入的参数是两个字符串,理论上也应该是合法的,加法行为可以是字符串相连。但是假如传入的两个参数是整数和字符串,或者整数和布尔值,意义就不太明确了,有可能引起程序崩溃
rust中的类型系统(trait的了解)_第2张图片
trait还可以用作抽象类型
相对于具体类型而言,抽象类型无法直接实例化,它的每个实例都是具体类型的实例。Rust目前有两种方法来处理抽象类型:trait对象和impl Trait。在泛型中使用trait限定,可以将任意类型的范围根据类型的行为限定到更精确可控的范围内。从这个角度出发,也可以将共同拥有相同行为的类型集合抽象为一个类型,这就是trait对象。模糊了类型和行为的界限,让开发者可以在多种类型之上按照行为统一抽象为抽象类型。抽象类型支持 trait 对象和 impl Trait语法,分别为动态分发和静态分发。

类型转换
隐式类型转换,如果不多加注意,可能会得到意料之外的结果。再比如C语言不同大小类型相互转换,长类型转换为短类型会造成溢出等问题。反观Rust语言,就不会因为类型转换出现安全问题。
Rust中的隐式类型转换基本上只有自动解引用。自动解引用的目的主要是方便开发者使用智能指针。Rust 中提供的 Box<T>、Rc<T>和 String 等类型,实际上是一种智能指针。它们的行为就像指针一样,可以通过“解引用”操作符进行解引用,来获取其内部的值进行操作。
一般来说,引用使用&操作符,而解引用使用*操作符。可以通过实现Deref trait来自定义解引用操作。Deref 有一个特性是强制隐式转换,规则是这样的:如果一个类型 T实现了Deref<Target=U>,则该类型T的引用(或智能指针)在应用的时候会被自动转换为类型U。
通过as关键字可以对原生类型进行安全的显式类型转换,但对一些自定义类型,还需要实现AsRef或From/Into这样的trait来支持显式类型转换

trait的更多知识

你可能感兴趣的:(rust,开发语言,后端)