16_Cairo1.0中的泛型(Generic)

16_Cairo1.0 中的泛型 (Generic)

此文章使用的 Cairo 编译器版本：2.0.0-rc0。因为 Cairo 正在快速更新，所以不同版本的语法会有些许不同，未来将会将文章内容更新到稳定版本。

泛型是一种编程语言特性，它允许在编写代码时使用类型参数，这些类型参数可以在代码实例化时被具体类型替换。

实际编程中，我们设计好了一套算法来高效地处理业务上的一些问题。如果没有泛型的话，每个类型就需要复制一份同一套算法的代码。理想情况下，算法应是和数据结构以及类型无关的，各种特殊的数据类型理应做好自己分内的工作，算法只关心一个标准的实现。

所以，泛型很酷😎

Cairo 中，泛型可以用在：函数、结构体、枚举 和 Trait 中的 Method 里面。

函数中的泛型

函数中如果传入包含泛型的参数，需要在参数前面的 <> 中声明泛型，而且这将作为函数签名的一部分。接下来我们来实现一个” 找出泛型数组中最小的泛型元素” 的功能：

Copy
use debug::PrintTrait;
use array::ArrayTrait;

// PartialOrd 实现泛型变量之间的比较
fn smallest_element<T, impl TPartialOrd: PartialOrd<T>, impl TCopy: Copy<T>, impl TDrop: Drop<T>>(
list: @Array<T>
) -> T {
// 这里使用了 * ，所以 T 必须实现了 copy trait
let mut smallest = *list[0];

let mut index = 1;

loop {
if index >= list.len() {
break smallest;
}
// 这里是两个泛型变量之间的比较，需要实现 PartialOrd
if *list[index] < smallest {
smallest = *list[index];
}
index = index + 1;
}
}

fn main() {
let mut list: Array<u8> = ArrayTrait::new();
list.append(5);
list.append(3);
list.append(10);

let s = smallest_element(@list);
assert(s == 3, 0);
s.print();
}

可以看到，我们在泛型声明区域，对泛型 T 添加了许多修饰（可以取名为 T，也可以取其他的名）。泛型可以是任意数据类型，如果数据类型能符合通用算法，势必就会对传入的数据类型有一定的约束，而我们在 <> 里添加的 impl，就是对传入此函数的泛型的约束。

首先，我们从 T 的 snapshot 中取值了，那么 T 就必须实现了 Copy trait；

其次，T 类型变量 smallest 最终作为函数的返回值，返回到 main 函数中，这步即包含了 move 操作，同时也有 Drop 操作，所以需要实现 Drop trait；

最后，我们需要比较两个泛型的大小，所以需要实现 PartialOrd trait。

所以我们看到函数中声明泛型的部位有这么一段： <T, impl TPartialOrd: PartialOrd<T>, impl TCopy: Copy<T>, impl TDrop: Drop<T>>。调用这个函数时，参数数组中的所有元素都必须实现这三个约束中描述的 trait。

结构体中的泛型

结构体元素中也可以放入泛型字段，如：

Copy
struct Wallet<T> {
balance: T
}

impl WalletDrop<T, impl TDrop: Drop<T>> of Drop<Wallet<T>>;

Copy
#[derive(Drop)]
struct Wallet<T> {
balance: T
}

以上两种方式，应该都可以。Cairo book 中说第二种方式不会将类型 T 声明为：实现了 Drop trait，但是没有给出一些案例代码。我实验了几次，目前都没有发现有什么区别，后续发现了再加上。

结构体 Method 中使用泛型

以下代码我们可以看到，在 struct、trait 和 impl 中都需要声明泛型，impl 中因为存储了算法逻辑，所以需要添加约束。

Copy
use debug::PrintTrait;

#[derive(Copy,Drop)]
struct Wallet<T> {
balance: T
}

trait WalletTrait<T> {
fn balance(self: @Wallet<T>) -> T;
}

impl WalletImpl<T, impl TCopy: Copy<T>> of WalletTrait<T>{
fn balance(self: @Wallet<T>) -> T{
*self.balance
}
}

fn main() {
let w = Wallet{balance:'100 000 000'};

w.balance().print();
}

再看一个同时使用两个不同泛型的例子：

Copy
use debug::PrintTrait;

#[derive(Copy,Drop)]
struct Wallet<T, U> {
balance: T,
address: U,
}

trait WalletTrait<T, U> {
fn getAll(self: @Wallet<T, U>) -> (T, U);
}

impl WalletImpl<T, impl TCopy: Copy<T>, U, impl UCopy: Copy> of WalletTrait<T, U>{
fn getAll(self: @Wallet<T, U>) -> (T, U){
(*self.balance,*self.address)
}
}

fn main() {
let mut w = Wallet{
balance: 100,
address: '0x0000aaaaa'
};

let (b,a) = w.getAll();
b.print();
a.print();
}

枚举中的泛型

刚好 Option 就是一个使用了泛型的枚举：

Copy
enum Option<T> {
Some: T,
None: (),
}