Contoh Program Menggunakan Struct
Untuk memahami kapan kita mungkin ingin menggunakan struct, mari kita tulis sebuah program yang menghitung luas dari sebuah persegi panjang. Kita akan mulai dengan menggunakan variabel tunggal, dan kemudian melakukan refaktor program hingga pada akhirnya kita menggunakan struct.
Let’s make a new project with Scarb called rectangles that will take the width and height of a rectangle specified in pixels and calculate the area of the rectangle. Listing 5-8 shows a short program with one way of doing exactly that in our project’s src/lib.cairo.
Filename: src/lib.cairo
fn main() {
let width = 30;
let height = 10;
let area = area(width, height);
println!("Area is {}", area);
}
fn area(width: u64, height: u64) -> u64 {
width * height
}
Listing 5-8: Calculating the area of a rectangle specified by separate width and height variables.
Sekarang jalankan program dengan scarb cairo-run
:
$ scarb cairo-run
Compiling listing_04_06_no_struct v0.1.0 (listings/ch05-using-structs-to-structure-related-data/listing_03_no_struct/Scarb.toml)
Finished `dev` profile target(s) in 3 seconds
Running listing_04_06_no_struct
Area is 300
Run completed successfully, returning []
Kode ini berhasil dalam menemukan luas dari persegi panjang dengan memanggil fungsi area
dengan setiap dimensi, tetapi kita dapat melakukan lebih untuk membuat kode ini lebih jelas dan mudah dibaca.
Permasalahan pada kode ini terlihat pada tanda tangan dari area
:
fn area(width: u64, height: u64) -> u64 {
The area
function is supposed to calculate the area of one rectangle, but the function we wrote has two parameters, and it’s not clear anywhere in our program that the parameters are related. It would be more readable and more manageable to group width and height together. We’ve already discussed one way we might do that in the Tuple Section of Chapter 2.
Refaktorisasi dengan Tuple
Listing 5-9 shows another version of our program that uses tuples.
Filename: src/lib.cairo
fn main() {
let rectangle = (30, 10);
let area = area(rectangle);
println!("Area is {}", area);
}
fn area(dimension: (u64, u64)) -> u64 {
let (x, y) = dimension;
x * y
}
Listing 5-9: Specifying the width and height of the rectangle with a tuple.
Dalam satu hal, program ini lebih baik. Tuple memungkinkan kita menambahkan sedikit struktur, dan sekarang kita hanya melewatkan satu argumen. Tetapi dalam hal lain, versi ini kurang jelas: tuple tidak memberi nama pada elemen-elemennya, sehingga kita harus mengindeks ke dalam bagian-bagian dari tuple, membuat perhitungan kita menjadi kurang jelas.
Bercampurnya lebar dan tinggi tidak akan masalah dalam perhitungan luas, tetapi jika kita ingin menghitung perbedaannya, hal ini akan menjadi masalah! Kita harus ingat bahwa width
adalah indeks 0
dari tuple dan height
adalah indeks 1
dari tuple. Ini akan menjadi lebih sulit bagi orang lain untuk memahami dan diingat jika mereka ingin menggunakan kode kita. Karena kita belum menyampaikan makna dari data kita dalam kode kita, sekarang lebih mudah untuk memperkenalkan kesalahan.
Refaktorisasi dengan Struct: Menambahkan Lebih Banyak Makna
Kita menggunakan struct untuk menambahkan makna dengan memberi label pada data. Kita dapat mengubah tuple yang kita gunakan menjadi sebuah struct dengan sebuah nama untuk keseluruhan serta nama-nama untuk bagian-bagiannya.
Filename: src/lib.cairo
struct Rectangle {
width: u64,
height: u64,
}
fn main() {
let rectangle = Rectangle { width: 30, height: 10 };
let area = area(rectangle);
println!("Area is {}", area);
}
fn area(rectangle: Rectangle) -> u64 {
rectangle.width * rectangle.height
}
Listing 5-10: Defining a Rectangle
struct.
Di sini kita telah mendefinisikan sebuah struct dan memberinya nama Rectangle
. Di dalam kurung kurawal, kita mendefinisikan field-field sebagai width
dan height
, yang keduanya memiliki tipe u64
. Kemudian, di main
, kita membuat sebuah instance khusus dari Rectangle
yang memiliki lebar 30
dan tinggi 10
. Fungsi area
kita sekarang didefinisikan dengan satu parameter, yang kita beri nama rectangle
yang bertipe struct Rectangle
. Kita dapat mengakses field-field dari instance tersebut dengan notasi titik, dan memberikan nama-nama yang deskriptif pada nilai-nilai tersebut daripada menggunakan nilai indeks tuple 0
dan 1
.
Conversions of Custom Types
We've already described how to perform type conversion on in-built types, see Data Types > Type Conversion. In this section, we will see how to define conversions for custom types.
Note: conversion can be defined for compound types, e.g. tuples, too.
Into
Defining a conversion for a custom type using the Into
trait will typically require specification of the type to convert into, as the compiler is unable to determine this most of the time. However this is a small trade-off considering we get the functionality for free.
// Compiler automatically imports the core library, so you can omit this import
use core::traits::Into;
#[derive(Drop, PartialEq)]
struct Rectangle {
width: u64,
height: u64,
}
#[derive(Drop)]
struct Square {
side_length: u64,
}
impl SquareIntoRectangle of Into<Square, Rectangle> {
fn into(self: Square) -> Rectangle {
Rectangle { width: self.side_length, height: self.side_length }
}
}
fn main() {
let square = Square { side_length: 5 };
// Compiler will complain if you remove the type annotation
let result: Rectangle = square.into();
let expected = Rectangle { width: 5, height: 5 };
assert!(
result == expected,
"A square is always convertible to a rectangle with the same width and height!",
);
}
TryInto
Defining a conversion for TryInto
is similar to defining it for Into
.
// Compiler automatically imports the core library, so you can omit this import
use core::traits::TryInto;
#[derive(Drop)]
struct Rectangle {
width: u64,
height: u64,
}
#[derive(Drop, PartialEq)]
struct Square {
side_length: u64,
}
impl RectangleIntoSquare of TryInto<Rectangle, Square> {
fn try_into(self: Rectangle) -> Option<Square> {
if self.height == self.width {
Option::Some(Square { side_length: self.height })
} else {
Option::None
}
}
}
fn main() {
let rectangle = Rectangle { width: 8, height: 8 };
let result: Square = rectangle.try_into().unwrap();
let expected = Square { side_length: 8 };
assert!(
result == expected,
"Rectangle with equal width and height should be convertible to a square.",
);
let rectangle = Rectangle { width: 5, height: 8 };
let result: Option<Square> = rectangle.try_into();
assert!(
result.is_none(),
"Rectangle with different width and height should not be convertible to a square.",
);
}