Rust 学习笔记之类型、泛型和 Trait

本文详细探讨了Rust编程语言中的类型系统、泛型和Trait。类型系统提供了语义约束,避免错误,而泛型允许编写适用于多种类型的代码,减少了代码重复。Trait是Rust中实现行为抽象的关键,它支持多态和代码重用。通过泛型函数、泛型结构体和枚举,以及泛型的impl,开发者可以创建高效、灵活的代码。同时,文章还介绍了如何使用和定义Trait,包括trait继承和与其他泛型结合使用的方法。
摘要由CSDN通过智能技术生成

Rust 的类型系统是比较吸引人的,实际上也算是一种代数类型系统(Algebraic data type),数学上有严格定义。

为什么有类型

计算机其实根本不关心类型。无论软件多么复杂,加上多少中间层,最终还是逃脱不了0和1。机器语言,是完全不关心类型的。我们也都清楚,这非常低效、冗长,特别容易出错。到了1950的时候,人们开始提出一些机器语言的助记符号,就有了现在仍然有用的汇编语言。汇编语言是一比一的翻译成机器语言的,没有编译的环境。

接下来是高级语言时代了,C语言等于在汇编语言上再加了一层抽象,C代码先编译成汇编语言,汇编语言再翻译成机器语言。我们都知道C语言是有类型的。类型和类型系统实际上提供了一种语义,指定什么是我想要的,什么是我不想要的,用来约束人类语言的模棱两可。

类型系统则是一套类型交互的规则。规则指明了哪些行为是正确的。Rust 的类型系统很大程度上受到了函数式编程的影响,比如 Ocaml 和 Haskell。比如,traits 就明显是 Haskell 的样子。

泛型

泛型提供了一种更高级的抽象。有的算法适用于多种类型,比如最简单的求和函数,这个算法就不关心是整数还是浮点数。如果在不支持的泛型的语言中,可能就需要把代码拷贝一遍,把参数作出相应的更改。然而,这样代码的维护起来的困难度就变大了。

泛型编程只适用在静态类型的编程语言中,最早出现在函数式编程语言 ML 中。动态语言,比如 Python,是不关心类型的,天生就带着泛型的气质,自然没有泛型的概念。静态语言有了泛型后,就可以用一个占位符比如 TKV 去代替具体类型声明,告诉编译器当初始化实例的时候再去填充具体的类型。

泛型函数

写一个简单的泛型函数,

// generic_function.rs

fn give_me<T>(value: T) {
   
  let _ = value;
}

fn main() {
   
  let a = "generics";
  let b = 1024;
  give_me(a);
  give_me(b);
}

在编译的时候,编译器会生成两个版本的 give_me,一个接受字符串类型,一个接受数字。可以通过 nm 命令来确定一下。

nm generic_function | grep "give"

nm 是 GNU 二进制工具包里的一个命令。通过把二进制文件传给 nm 然后使用 grep 过滤前缀为 give 的函数。

give 函数

可以发现,泛型提供了一种简单的粗暴的多态。编译代码后自动生成多个版本的函数,这也带来了一个问题,由于代码的重复,最终可执行文件的大小会增加。不过大多数时候,使用泛型实现多态性是首选的方式,因为运行的时候没有开销。

如果能用泛型解决的问题,应该尽可能使用泛型。只有泛型不能满足要求,例如需要在某个集合容器里存取一组类型,才应该考虑用 trait。

泛型结构体和泛型枚举

和泛型函数很类似,没什么太大的差别,语法上没什么难以理解的:

struct GenericsStruct<T>(T);

struct Container<T> {
   
  item: T;
}

enum Transmission<T> {
   
  Signal(T),
  NoSignal
}

fn main() {
   
  // ...
}

泛型的 impl

同样可以使用 impl 来为泛型实现相应的方法。

struct Container<T> {
   
  item: T
}

impl<T> Container<T> {
   
  fn new(item: T) -> Self {
   
    Container {
    item }
  }
}

这个方法适用于所有类型,但是有时候也可以实现特定类型的方法:

impl Container<u32> {
   
  fn sum(item: u32) -> Self {
   
    Container {
    item }
  }
}

因为这个方法只针对 u32,所以不需要写 impl<T>,只需要写 impl 就可以了。

使用泛型

要使用泛型,需要在编译时告诉编译器具体类型是什么,编译器才可以把 T 这种占位符替换成对应的类型。如果没有指明类型,代码是无法正确编译的:

// creating_generic_vec.rs

fn main() {
   
  let a = Vec::new();
}

无法正确编译

有几种方法可以告知编译器具体要什么类型:

// creating_generic_vec.rs

fn main() {
   
  // providing a type
  let v1: Vec<u8> = Vec::new();

  // or calling method
  let mut v2 = Vec::new();
  v2.push(2); // v2 is new Vec<i32>

  // or using turbofish
  let v3 = Vec::<u8>::new(); // not so readable
}

编程中有一个很常见的操作,把数字字符串解析成数字。Rust 提供了一个泛型的 parse 方法,通过指定类型,如i32f32usize,转换成不同的类型。

// using_generic.rs

use std::
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值