16_Cairo1.0 中的泛型 (Generic)#
此文章使用的 Cairo 編譯器版本:2.0.0-rc0。因為 Cairo 正在快速更新,所以不同版本的語法會有些許不同,未來將會將文章內容更新到穩定版本。
泛型是一種編程語言特性,它允許在編寫代碼時使用類型參數,這些類型參數可以在代碼實例化時被具體類型替換。
實際編程中,我們設計好了一套算法來高效地處理業務上的一些問題。如果沒有泛型的話,每個類型就需要複製一份同一套算法的代碼。理想情況下,算法應是和數據結構以及類型無關的,各種特殊的數據類型理應做好自己分內的工作,算法只關心一個標準的實現。
所以,泛型很酷😎
Cairo 中,泛型可以用在:函數、結構體、枚舉 和 Trait 中的 Method 裡面。
函數中的泛型#
函數中如果傳入包含泛型的參數,需要在參數前面的 <>
中聲明泛型,而且這將作為函數簽名的一部分。接下來我們來實現一個” 找出泛型數組中最小的泛型元素” 的功能:
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。
結構體中的泛型#
結構體元素中也可以放入泛型字段,如:
struct Wallet<T> {
balance: T
}
impl WalletDrop<T, impl TDrop: Drop<T>> of Drop<Wallet<T>>;
#[derive(Drop)]
struct Wallet<T> {
balance: T
}
以上兩種方式,應該都可以。Cairo book 中說第二種方式不會將類型 T 声明為:實現了 Drop trait,但是沒有給出一些案例代碼。我實驗了幾次,目前都沒有發現有什麼區別,後續發現了再加上。
結構體 Method 中使用泛型#
以下代碼我們可以看到,在 struct、trait 和 impl 中都需要聲明泛型,impl 中因為存儲了算法邏輯,所以需要添加約束。
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();
}
再看一個同時使用兩個不同泛型的例子:
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<U>> 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 就是一個使用了泛型的枚舉:
enum Option<T> {
Some: T,
None: (),
}