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 のブックでは、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: (),
}