Глава 3 День 2 - Ресурсы в словарях и массивах
Привет, друзья! Сегодня мы будем использовать наши знания ресурсов и применять его к массивам и словарям - то, что мы рассматривали в главе 2. Сами по себе они могут быть довольно простыми в обращении, но если собрать их вместе, все становится немного сложнее.
Видео
Вы можете посмотреть это видео с 08:00 и до конца (начало мы рассматривали в предыдущем дне): https://www.youtube.com/watch?v=SGa2mnDFafc
Для чего нужны словари и массивы?
Прежде всего, почему мы говорим о ресурсах в словарях, а не о ресурсах в структурах? С самого начала важно отметить, что вы не можете хранить ресурсы внутри структуры. Хотя структура - это контейнер данных, но мы не можем поместить в нее ресурсы.
Хорошо. Тогда где мы можем хранить ресурс?
- В словаре или массиве
- Внутри другого ресурса
- В качестве переменной состояния контракта
- Внутри аккаунта (об этом мы поговорим позже)
Вот и все. Сегодня мы поговорим о первом пункте.
Ресурсы в массивах
Всегда лучше учиться на примерах, поэтому давайте откроем игровую площадку Flow и развернем контракт, который мы использовали в Главе 3 День 1:
pub contract Test {
pub resource Greeting {
pub let message: String
init() {
self.message = "Hello, Mars!"
}
}
}
Пока у нас есть только 1 ресурс с типом @Greeting
. Круто! Теперь давайте попробуем создать переменную состояния, которая хранит список приветствий в массиве.
pub contract Test {
pub var arrayOfGreetings: @[Greeting]
pub resource Greeting {
pub let message: String
init() {
self.message = "Hello, Mars!"
}
}
init() {
self.arrayOfGreetings <- []
}
}
Обратите внимание на тип arrayOfGreetings
: @[Greeting]
. Вчера мы узнали, что ресурсы всегда имеют символ @
перед собой. Это относится и к типам массивов, внутри которых есть ресурсы. Вы должны сказать Cadence, что это массив ресурсов, поставив перед ним символ @
. И вы должны убедиться, что @
находится за скобками, а не внутри.
[@Greeting]
- неверная запись
@[Greeting]
- верная запись
Также обратите внимание, что внутри функции init
мы инициализируем ее с помощью оператора <-
, а не =
. И снова, когда мы имеем дело с ресурсами (будь они в массивах, словарях или сами по себе), мы должны использовать <-
.
Добавление в массив
Класс! Мы создали свой собственный массив ресурсов. Давайте рассмотрим, как добавить ресурс в массив.
ПРИМЕЧАНИЕ: Сегодня мы будем передавать ресурсы в качестве аргументов нашим функциям. Это означает, что нам неважно, как были созданы ресурсы, мы просто используем примеры функций, чтобы показать вам, как добавлять в массивы и в словари.
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 <- []
}
}
В этом примере мы добавили новую функцию addGreeting
, которая принимает тип @Greeting
и добавляет его в массив с помощью функции append
. Кажется, все достаточно просто, верно? Именно так и выглядит обычное добавление в массив, мы просто используем оператор <-
для “перемещения” ресурса в массив.
Удаление из массива
Хорошо, мы добавили ресурс в массив. Теперь как удалить из него ресурс?
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 <- []
}
}
И снова все довольно просто. В обычном массиве вы используете функцию remove
для удаления элемента. То же самое происходит и с ресурсами, с той лишь разницей, что вы используете функцию <-
для “перемещения” ресурса из массива. Невероятно!
Ресурсы в словарях
Ресурсы в словарях немного сложнее. Одна из причин этого заключается в том, что, если вы помните из Главы 2 День 3, словари всегда возвращают необязательный тип, когда вы обращаетесь к значениям внутри них. Это делает хранение и извлечение ресурсов гораздо более сложным. В любом случае, я бы сказал, что ресурсы чаще всего хранятся в словарях, поэтому важно узнать, как это делается.
Для примера возьмем данный контракт:
pub contract Test {
pub var dictionaryOfGreetings: @{String: Greeting}
pub resource Greeting {
pub let message: String
init() {
self.message = "Hello, Mars!"
}
}
init() {
self.dictionaryOfGreetings <- {}
}
}
У нас будет словарь, который сопоставляет message
с ресурсом @Greeting
, содержащим это сообщение. Обратите внимание на тип словаря: @{String: Greeting}
. Символ @
находится за пределами фигурных скобок.
Добавление к словарю
Существует два различных способа добавления ресурса в словарь. Давайте рассмотрим оба.
#1 - Самый простой, но строгий
Самый простой способ добавить ресурс в словарь - это использовать оператор “принудительного перемещения” <-!
, например, так:
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 <- {}
}
}
В функции addGreeting
мы сначала получаем key
, обращаясь к message
внутри нашего greeting
. Затем мы добавляем в словарь, “принудительно перемещая” greeting
в словарь dictionaryOfGreetings
по определенному ключу
.
Оператор принудительного перемещения <-!
в основном означает: “Если в месте назначения уже есть значение, то паникуйте(panic) и прерывайте программу. В противном случае поместите его туда”.
#2 - Сложный, но может работать с копиями
Второй способ перемещения ресурса в словарь - это использование синтаксиса двойного перемещения, как показано ниже:
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 <- {}
}
}
В этом примере вы можете видеть, как происходит странное действие оператора двойного перемещения. Что это значит? Давайте разделим это на шаги:
- Берем любое значение, которое находится в определенном
key
, и перемещаем его вoldGreeting
. - Теперь, когда мы знаем, что ничто не сопоставлено с
key
, переместимgreeting
в это место - Уничтожение
oldGreeting
По сути, этот способ более раздражающий и выглядит странно, но он позволяет обрабатывать случай, когда значение уже есть. В приведенном выше случае мы просто уничтожаем ресурс, но при желании вы можете ничего с ним не делать.
Удаление из словаря
Вот как можно удалить ресурс из словаря:
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 <- {}
}
}
Помните, в разделе “Удаление из массива” все, что нам нужно было сделать, это вызвать функцию remove
. В словарях доступ к элементу возвращает необязательный элемент, поэтому мы должны как-то “развернуть” его. Если бы мы просто напишем это…
pub fun removeGreeting(key: String): @Greeting {
let greeting <- self.dictionaryOfGreetings.remove(key: key)
return <- greeting
}
мы получим ошибку: “Несовпадение типов. Ожидалось Test.Greeting
, получено Test.Greeting?
”. Чтобы исправить это, мы можем использовать либо panic
, либо оператор принудительного разворачивания !
, например, так:
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
}
Заключение
На сегодня это все! :D Теперь, возможно, вы зададитесь вопросом: “А что если я хочу получить доступ к элементу массива/словаря, который содержит ресурс, и что-то с ним сделать?“. Вы можете это сделать, но сначала вам нужно будет переместить ресурс из массива/словаря, сделать что-то, а затем переместить его обратно. Завтра мы поговорим о ссылках, которые позволят вам проводить действия с ресурсами без необходимости перемещать их повсюду. Счастливо!
Квесты
На сегодня у вас будет 1 большой квест вместо нескольких маленьких.
- Напишите собственный смарт-контракт, который содержит две переменные состояния: массив ресурсов и словарь ресурсов. Добавьте функции для удаления и добавления в каждую из них. Они должны отличаться от приведенных выше примеров.