16_Cairo1.0 におけるジェネリック (Generic)#
この文章で使用されている Cairo コンパイラのバージョン:2.0.0-rc0。Cairo は急速に更新されているため、異なるバージョンの構文には若干の違いがあります。将来的には、記事の内容を安定版に更新します。
ジェネリックは、コードを書く際に型パラメータを使用することを許可するプログラミング言語の特性です。これらの型パラメータは、コードのインスタンス化時に具体的な型に置き換えることができます。
実際のプログラミングでは、ビジネス上のいくつかの問題を効率的に処理するためのアルゴリズムを設計します。ジェネリックがなければ、各型に対して同じアルゴリズムのコードをコピーする必要があります。理想的には、アルゴリズムはデータ構造や型に依存せず、さまざまな特殊なデータ型は自分の役割を果たすべきであり、アルゴリズムは標準的な実装にのみ関心を持つべきです。
したがって、ジェネリックはクールです😎
Cairo では、ジェネリックは:関数、構造体、列挙型、および Trait 内のメソッドで使用できます。
関数内のジェネリック#
関数にジェネリックを含む引数を渡す場合、引数の前の <> 内でジェネリックを宣言する必要があり、これは関数のシグネチャの一部となります。次に、「ジェネリック配列の中で最小のジェネリック要素を見つける」機能を実装してみましょう:
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;
}
// ここは2つのジェネリック変数間の比較で、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 のスナップショットから値を取得したので、T は Copy trait を実装している必要があります;
- 次に、T 型変数 smallest は最終的に関数の戻り値として main 関数に返されます。このステップには move 操作が含まれ、同時に Drop 操作もあるため、Drop trait を実装する必要があります;
- 最後に、2 つのジェネリックの大きさを比較する必要があるため、PartialOrd trait を実装する必要があります。
したがって、関数内でジェネリックを宣言する部分には次のような記述があります: <T, impl TPartialOrd: PartialOrd<T>, impl TCopy: Copy<T>, impl TDrop: Drop<T>>。この関数を呼び出すとき、引数配列内のすべての要素はこれら 3 つの制約で説明される trait を実装している必要があります。
構造体内のジェネリック#
構造体の要素にもジェネリックフィールドを含めることができます。例えば:
struct Wallet<T> {
balance: T
}
impl WalletDrop<T, impl TDrop: Drop<T>> of Drop<Wallet<T>>;
#[derive(Drop)]
struct Wallet<T> {
balance: T
}
上記の 2 つの方法はどちらも可能です。Cairo book では、2 番目の方法は型 T を Drop trait を実装したものとして宣言しないと述べていますが、具体的なサンプルコードは示されていません。何度か実験しましたが、現在のところ違いは見つかりませんでした。後で何か見つけたら追加します。
構造体メソッド内でのジェネリックの使用#
以下のコードでは、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();
}
次に、2 つの異なるジェネリックを同時に使用する例を見てみましょう:
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: (),
}