Course Overview

Глава 3 День 2 - Ресурсы в словарях и массивах

Привет, друзья! Сегодня мы будем использовать наши знания ресурсов и применять его к массивам и словарям - то, что мы рассматривали в главе 2. Сами по себе они могут быть довольно простыми в обращении, но если собрать их вместе, все становится немного сложнее.

Видео

Вы можете посмотреть это видео с 08:00 и до конца (начало мы рассматривали в предыдущем дне): https://www.youtube.com/watch?v=SGa2mnDFafc

Для чего нужны словари и массивы?

Прежде всего, почему мы говорим о ресурсах в словарях, а не о ресурсах в структурах? С самого начала важно отметить, что вы не можете хранить ресурсы внутри структуры. Хотя структура - это контейнер данных, но мы не можем поместить в нее ресурсы.

Хорошо. Тогда где мы можем хранить ресурс?

  1. В словаре или массиве
  2. Внутри другого ресурса
  3. В качестве переменной состояния контракта
  4. Внутри аккаунта (об этом мы поговорим позже)

Вот и все. Сегодня мы поговорим о первом пункте.

Ресурсы в массивах

Всегда лучше учиться на примерах, поэтому давайте откроем игровую площадку Flow и развернем контракт, который мы использовали в Главе 3 День 1:

cadence
		
			pub contract Test {

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

}
		 
	

Пока у нас есть только 1 ресурс с типом @Greeting. Круто! Теперь давайте попробуем создать переменную состояния, которая хранит список приветствий в массиве.

cadence
		
			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 мы инициализируем ее с помощью оператора <-, а не =. И снова, когда мы имеем дело с ресурсами (будь они в массивах, словарях или сами по себе), мы должны использовать <-.

Добавление в массив

Класс! Мы создали свой собственный массив ресурсов. Давайте рассмотрим, как добавить ресурс в массив.

ПРИМЕЧАНИЕ: Сегодня мы будем передавать ресурсы в качестве аргументов нашим функциям. Это означает, что нам неважно, как были созданы ресурсы, мы просто используем примеры функций, чтобы показать вам, как добавлять в массивы и в словари.

cadence
		
			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. Кажется, все достаточно просто, верно? Именно так и выглядит обычное добавление в массив, мы просто используем оператор <- для “перемещения” ресурса в массив.

Удаление из массива

Хорошо, мы добавили ресурс в массив. Теперь как удалить из него ресурс?

cadence
		
			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, словари всегда возвращают необязательный тип, когда вы обращаетесь к значениям внутри них. Это делает хранение и извлечение ресурсов гораздо более сложным. В любом случае, я бы сказал, что ресурсы чаще всего хранятся в словарях, поэтому важно узнать, как это делается.

Для примера возьмем данный контракт:

cadence
		
			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 - Самый простой, но строгий

Самый простой способ добавить ресурс в словарь - это использовать оператор “принудительного перемещения” <-!, например, так:

cadence
		
			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 - Сложный, но может работать с копиями

Второй способ перемещения ресурса в словарь - это использование синтаксиса двойного перемещения, как показано ниже:

cadence
		
			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 <- {}
    }

}
		 
	

В этом примере вы можете видеть, как происходит странное действие оператора двойного перемещения. Что это значит? Давайте разделим это на шаги:

  1. Берем любое значение, которое находится в определенном key, и перемещаем его в oldGreeting.
  2. Теперь, когда мы знаем, что ничто не сопоставлено с key, переместим greeting в это место
  3. Уничтожение oldGreeting

По сути, этот способ более раздражающий и выглядит странно, но он позволяет обрабатывать случай, когда значение уже есть. В приведенном выше случае мы просто уничтожаем ресурс, но при желании вы можете ничего с ним не делать.

Удаление из словаря

Вот как можно удалить ресурс из словаря:

cadence
		
			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. В словарях доступ к элементу возвращает необязательный элемент, поэтому мы должны как-то “развернуть” его. Если бы мы просто напишем это…

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

мы получим ошибку: “Несовпадение типов. Ожидалось Test.Greeting, получено Test.Greeting?”. Чтобы исправить это, мы можем использовать либо panic, либо оператор принудительного разворачивания !, например, так:

cadence
		
			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 большой квест вместо нескольких маленьких.

  1. Напишите собственный смарт-контракт, который содержит две переменные состояния: массив ресурсов и словарь ресурсов. Добавьте функции для удаления и добавления в каждую из них. Они должны отличаться от приведенных выше примеров.