китаев.tech

Arrays — push, pop, итерация

Как использовать массивы в Solidity: добавлять элементы через push, удалять последний через pop и обходить массив циклом.

Массив хранит список значений

Array в Solidity — это упорядоченный список. У каждого элемента есть индекс, а у массива есть length.

Массив подходит, когда нужно сохранить порядок: список задач, список участников, историю действий или набор адресов, который потом надо прочитать целиком.


Пишем список задач

Создайте файл contracts/TaskList.sol:

solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract TaskList {
    struct Task {
        string text;
        bool completed;
        address createdBy;
    }

    Task[] private tasks;

    event TaskAdded(uint256 indexed index, string text, address indexed createdBy);
    event TaskCompleted(uint256 indexed index);
    event LastTaskRemoved(uint256 indexed index);

    error EmptyText();
    error InvalidIndex(uint256 index);
    error EmptyArray();

    function addTask(string calldata text) external {
        if (bytes(text).length == 0) {
            revert EmptyText();
        }

        tasks.push(
            Task({
                text: text,
                completed: false,
                createdBy: msg.sender
            })
        );

        emit TaskAdded(tasks.length - 1, text, msg.sender);
    }

    function completeTask(uint256 index) external {
        if (index >= tasks.length) {
            revert InvalidIndex(index);
        }

        tasks[index].completed = true;

        emit TaskCompleted(index);
    }

    function removeLastTask() external {
        if (tasks.length == 0) {
            revert EmptyArray();
        }

        uint256 lastIndex = tasks.length - 1;
        tasks.pop();

        emit LastTaskRemoved(lastIndex);
    }

    function getTask(uint256 index) external view returns (Task memory) {
        if (index >= tasks.length) {
            revert InvalidIndex(index);
        }

        return tasks[index];
    }

    function taskCount() external view returns (uint256) {
        return tasks.length;
    }

    function getAllTasks() external view returns (Task[] memory) {
        return tasks;
    }

    function countCompletedTasks() external view returns (uint256 completedCount) {
        uint256 length = tasks.length;

        for (uint256 i = 0; i < length; i++) {
            if (tasks[i].completed) {
                completedCount++;
            }
        }
    }
}

Этот контракт хранит задачи в массиве, добавляет новые через push, удаляет последнюю через pop и проходит по массиву циклом, чтобы посчитать выполненные задачи.


Как это работает

Task[] private tasks — динамический массив структур. Его длина меняется во время работы контракта.

tasks.push(...) — добавляет новый элемент в конец массива. После push индекс нового элемента равен tasks.length - 1.

tasks[index] — читает или изменяет элемент по индексу. Индексы начинаются с 0, поэтому последний элемент находится по индексу tasks.length - 1.

tasks.pop() — удаляет последний элемент и уменьшает length на 1. pop не умеет удалить элемент из середины массива.

for (uint256 i = 0; i < length; i++) — проходит по всем элементам массива. В примере цикл находится в view-функции, поэтому он не меняет состояние и не стоит gas при обычном чтении через RPC.

Task memory — копия задачи из storage в память. Такую копию удобно вернуть из view-функции наружу.


Что важно знать про массивы

Динамический массив в storage хранит length и элементы. Solidity проверяет выход за границы: если обратиться к tasks[999], когда элементов меньше, вызов откатится.

Большие массивы опасно обходить в транзакциях. Чем больше элементов в цикле, тем больше gas. Если массив станет слишком большим, транзакция может перестать помещаться в block gas limit.

getAllTasks() удобно для маленьких списков и локальных примеров. Для больших данных лучше читать элементы постранично: сначала taskCount(), затем getTask(index) для нужного диапазона.


Частые ошибки

Удалять через pop не тот элементpop удаляет только последний элемент. Для удаления из середины нужен отдельный паттерн: сдвиг элементов или замена удаляемого элемента последним.

Итерировать большой массив в write-функции → такой вызов может стать слишком дорогим или начать падать по gas. Ограничивайте размер массива или делайте обработку частями.

Забывать проверку индекса → перед чтением tasks[index] проверяйте index < tasks.length, чтобы вернуть понятную ошибку вместо низкоуровневого отката.


Что дальше

Материалы китаev.tech публикуются в образовательных целях и не являются инвестиционной рекомендацией. Примеры кода и описания протоколов — для обучения; использование в продакшне на ваш собственный риск. Дисклеймер и политика конфиденциальности.