Tổng quan

Trong bài viết này, chúng ta đã tìm hiểu về tài nguyên (Resource) trong từ điển (Dictionary) và mảng (Array) trong ngôn ngữ Cadence của Flow Blockchain. Chúng ta đã nghiên cứu cách lưu trữ, thêm và xóa tài nguyên trong mảng và từ điển thông qua ví dụ cụ thể. Một số điểm quan trọng cần nhớ:

  1. Ký hiệu @ phải được sử dụng cho tài nguyên và đặt ngoài dấu ngoặc của kiểu mảng và từ điển.
  2. Sử dụng toán tử di chuyển <- khi thao tác với tài nguyên, bất kể chúng nằm trong mảng, từ điển hay riêng lẻ.
  3. Đối với tài nguyên trong từ điển, chúng ta cần xử lý việc truy cập phần tử trả về kiểu tùy chọn bằng cách sử dụng panic hoặc toán tử force-unwrap !.

Lập trình ngôn ngữ Cadence

Từ điển và mảng?

Đầu tiên, tại sao chúng ta lại nói về resource trong từ điển, mà không phải là resource trong các cấu trúc (struct)? Điều quan trọng cần lưu ý là bạn không thể lưu trữ resource bên trong một cấu trúc. Mặc dù cấu trúc là một hộp chứa dữ liệu, nhưng chúng ta không thể đặt tài nguyên vào bên trong chúng.

Vậy chúng ta có thể lưu trữ tài nguyên ở đâu?

  1. Bên trong một từ điển hoặc mảng
  2. Bên trong một resource khác
  3. Là một biến trạng thái hợp đồng (contract state variable)
  4. Bên trong bộ nhớ tài khoản.

Resource trong mảng?

Luôn tốt hơn khi học thông qua ví dụ, vậy hãy mở một môi trường chơi Flow (Flow playground) và triển khai hợp đồng:

pub contract Test {

    pub resource Greeting {
        pub let message: String
        init() {
            self.message = "Hello, Mars!"
        }
    }

}

Cho đến giờ, chúng ta chỉ có 1 tài nguyên với kiểu @Greeting. Bây giờ, hãy thử có một biến trạng thái lưu trữ danh sách Greetings trong một mảng.

pub contract Test {

    pub var arrayOfGreetings: @[Greeting]

    pub resource Greeting {
        pub let message: String
        init() {
            self.message = "Hello, Mars!"
        }
    }

    init() {
        self.arrayOfGreetings <- []
    }

}

Hãy chú ý đến kiểu của arrayOfGreetings: @[Greeting]. Chúng ta đã học ngày hôm qua rằng tài nguyên luôn có ký hiệu @ phía trước. Điều này cũng áp dụng cho các kiểu mảng có tài nguyên bên trong, bạn phải thông báo cho Cadence rằng đó là một mảng tài nguyên bằng cách đặt @ phía trước. Và bạn phải chắc chắn rằng @ nằm ngoài dấu ngoặc, không phải bên trong.

[@Greeting] – điều này không đúng

@[Greeting] – điều này đúng

Cũng lưu ý rằng bên trong hàm init, chúng ta khởi tạo nó bằng toán tử <-, không phải =. Một lần nữa, khi xử lý các tài nguyên (cho dù chúng ở trong mảng, từ điển hay riêng), chúng ta phải sử dụng <-.

Thêm vào mảng

Chúng ta đã tạo ra Array Resource của riêng mình. Hãy xem cách thêm Resource vào một Array.

LƯU Ý: Hôm nay, chúng ta sẽ chuyển các Resource xung quanh làm đối số cho các chức năng của chúng ta. Điều này có nghĩa là chúng ta không lo lắng về cách tạo tài nguyên, chỉ sử dụng các hàm mẫu để chỉ cho bạn cách thêm vào mảng và từ điển.

pub contract Test {

    pub var arrayOfGreetings: @[Greeting]

    pub resource Greeting {
        pub let message: String
        init() {
            self.message = "Hello, Mars!"
        }
    }

    pub fun addGreeting(greeting: @Greeting) {
        self.arrayOfGreetings.append(<- greeting)
    }

    init() {
        self.arrayOfGreetings <- []
    }

}

Trong ví dụ này, chúng ta đã thêm một hàm mới addGreeting có kiểu @Greeting và thêm nó vào mảng bằng cách sử dụng hàm append. Có vẻ đủ dễ dàng phải không? Đây chính xác là giao diện của nó khi thêm vào một mảng thông thường, chúng ta chỉ cần sử dụng toán tử <- để “di chuyển” tài nguyên vào trong mảng.

Xóa từ mảng

Được rồi, chúng ta đã thêm vào mảng. Bây giờ làm cách nào để xóa tài nguyên khỏi nó?

pub contract Test {

    pub var arrayOfGreetings: @[Greeting]

    pub resource Greeting {
        pub let message: String
        init() {
            self.message = "Hello, Mars!"
        }
    }

    pub fun addGreeting(greeting: @Greeting) {
        self.arrayOfGreetings.append(<- greeting)
    }

    pub fun removeGreeting(index: Int): @Greeting {
        return <- self.arrayOfGreetings.remove(at: index)
    }

    init() {
        self.arrayOfGreetings <- []
    }

}

Một lần nữa, nó khá đơn giản. Trong một mảng bình thường, bạn sẽ sử dụng hàm remove để loại bỏ một phần tử. Đối với tài nguyên cũng vậy, điểm khác biệt duy nhất là bạn sử dụng <- để “di chuyển” tài nguyên ra khỏi mảng.

Resources trong Dictionaries

Tài nguyên trong từ điển phức tạp hơn một chút. Một trong những lý do cho điều này là bởi vì, các từ điển luôn trả về các tùy chọn khi bạn truy cập các giá trị bên trong nó. Điều này làm cho việc lưu trữ và truy xuất tài nguyên trở nên khó khăn hơn rất nhiều. Dù bằng cách nào, ta muốn nói rằng các tài nguyên thường được lưu trữ trong từ điển, vì vậy điều quan trọng là phải tìm hiểu cách thực hiện.

Hãy sử dụng một hợp đồng tương tự cho ví dụ này:

pub contract Test {

    pub var dictionaryOfGreetings: @{String: Greeting}

    pub resource Greeting {
        pub let message: String
        init() {
            self.message = "Hello, Mars!"
        }
    }

    init() {
        self.dictionaryOfGreetings <- {}
    }

}

Chúng ta sẽ có một từ điển ánh xạ một tin nhắn tới tài nguyên @Greeting chứa tin nhắn đó. Lưu ý loại từ điển: @{String: Greeting}. @ nằm ngoài dấu ngoặc nhọn.

Thêm vào Dictionary

Có 2 cách khác nhau để thêm tài nguyên vào từ điển. Hãy nhìn vào cả hai.

#1 – Dễ nhất nhưng nghiêm ngặt
Cách dễ nhất để thêm tài nguyên vào từ điển là sử dụng toán tử “force-move” <-!, như sau:

pub contract Test {

    pub var dictionaryOfGreetings: @{String: Greeting}

    pub resource Greeting {
        pub let message: String
        init() {
            self.message = "Hello, Mars!"
        }
    }

    pub fun addGreeting(greeting: @Greeting) {
        let key = greeting.message
        self.dictionaryOfGreetings[key] <-! greeting
    }

    init() {
        self.dictionaryOfGreetings <- {}
    }

}

Trong hàm addGreeting, trước tiên chúng ta lấy key bằng cách truy cập message bên trong greeting của chúng ta. Sau đó, chúng ta thêm vào từ điển bằng cách “bắt buộc di chuyển” greeting vào từ điển dictionaryOfGreetingskey cụ thể.

Toán tử di chuyển lực <-! về cơ bản có nghĩa là: “Nếu đã có một giá trị ở đích, hãy hoảng sợ và hủy bỏ chương trình. Nếu không, hãy đặt nó ở đó.”

#2 – Phức tạp, nhưng xử lý trùng lặp
Cách thứ hai để di chuyển tài nguyên vào từ điển là sử dụng cú pháp di chuyển kép, như sau:

pub contract Test {

    pub var dictionaryOfGreetings: @{String: Greeting}

    pub resource Greeting {
        pub let message: String
        init() {
            self.message = "Hello, Mars!"
        }
    }

    pub fun addGreeting(greeting: @Greeting) {
        let key = greeting.message
        
        let oldGreeting <- self.dictionaryOfGreetings[key] <- greeting
        destroy oldGreeting
    }

    init() {
        self.dictionaryOfGreetings <- {}
    }

}

Trong ví dụ này, bạn có thể thấy một số điều toán tử di chuyển kép kỳ lạ đang xảy ra. Nó có nghĩa là gì? Hãy chia nó thành các bước:

  1. Lấy bất kỳ giá trị nào ở key cụ thể và chuyển nó vào oldGreeting
  2. Bây giờ chúng tôi biết không có gì được mapping với key, hãy di chuyển greeting đến vị trí đó
  3. Phá hủy cái oldGreeting

Về bản chất, cách này khó chịu hơn và trông kỳ quặc hơn, nhưng nó cho phép bạn xử lý trường hợp đã có giá trị ở đó. Trong trường hợp trên, chúng tôi chỉ cần hủy tài nguyên, nhưng nếu bạn muốn, bạn có thể làm bất cứ điều gì khác.

Removing from a Dictionary

pub contract Test {

    pub var dictionaryOfGreetings: @{String: Greeting}

    pub resource Greeting {
        pub let message: String
        init() {
            self.message = "Hello, Mars!"
        }
    }

    pub fun addGreeting(greeting: @Greeting) {
        let key = greeting.message
        
        let oldGreeting <- self.dictionaryOfGreetings[key] <- greeting
        destroy oldGreeting
    }

    pub fun removeGreeting(key: String): @Greeting {
        let greeting <- self.dictionaryOfGreetings.remove(key: key) ?? panic("Could not find the greeting!")
        return <- greeting
    }

    init() {
        self.dictionaryOfGreetings <- {}
    }

}

Hãy nhớ rằng trong phần ‘Xóa khỏi Mảng’, tất cả những gì chúng ta phải làm là gọi hàm remove. Trong từ điển, việc truy cập một phần tử trả về một tùy chọn, vì vậy chúng ta phải “mở gói” nó bằng cách nào đó. Nếu chúng ta vừa viết điều này …

pub fun removeGreeting(key: String): @Greeting {
    let greeting <- self.dictionaryOfGreetings.remove(key: key)
    return <- greeting
}

chúng ta sẽ gặp lỗi: “Mismatched types. Expected Test.Greeting, got Test.Greeting?” Để khắc phục, chúng ta có thể sử dụng panic hoặc toán tử force-unwrap !, như sau:

pub fun removeGreeting(key: String): @Greeting {
    let greeting <- self.dictionaryOfGreetings.remove(key: key) ?? panic("Could not find the greeting!")
    // OR...
    // let greeting <- self.dictionaryOfGreetings.remove(key: key)!
    return <- greeting
}

Cộng đồng hệ sinh thái Flow Việt Nam