Кодирование на Solidity не всегда интуитивно понятно, но весело. Одной из небольших проблем, над которыми мне пришлось задуматься, была разница между хранилищем и памятью.

Представьте себе простой пример, подобный приведенному ниже

pragma solidity ^0.4.22;
contract Fruits {
    string[] public items;
    constructor () public {
        items.push('apple');
        items.push('orange');
    }
}

Если бы мне пришлось развернуть этот контракт, элементы с индексами 0 и 1 были бы «яблочными» и «оранжевыми» соответственно. Достаточно просто.

Что, если я попытаюсь создать новый массив внутри моей функции-конструктора, указывающий на массив itemsvariable? Кажется, достаточно просто.

constructor () public {
  ...
  string[] newItems = items;
}

Но ждать! мы получаем предупреждение, подобное приведенному ниже!

Понятно предупреждение? Я тоже.

Чтобы полностью понять, что происходит, давайте обсудим, как Solidity интерпретирует память и хранилище.

Хранилище против памяти

Solidity рассматривает взаимосвязь между памятью и памятью двумя разными способами.

  1. Данные о состоянии контракта
  • хранилище: переменные, определенные на верхнем уровне внутри контракта. (например: items)
  • память: структуры (см. http://solidity.readthedocs.io/en/v0.4.21/types.html, если вы не знаете, что такое структура)

2. Объявление значения переменной

  • В этом сценарии объявление значения переменной может быть определено как хранилище или память в зависимости от того, как вы хотите сохранить эту переменную (более подробно описано ниже).

В нашем примере items - это состояние хранения внутри контракта Fruits. С новой переменной newItems у нас есть два варианта: сохранить переменную в памяти или хранилище. Я рассмотрю оба варианта ниже.

NewItems как хранилище

Если newItems был сохранен как хранилище, вы должны включить ключевое слово storage перед newItems. Пример показан ниже.

string[] storage newItems = items;

При добавлении значения для хранения предупреждение должно исчезнуть. Но что именно это делает? Добавив storage, newItems теперь ТОЧКИ в массив items. Другими словами, любые изменения, которые вы вносите в newItems, напрямую повлияют на массив items.

pragma solidity ^0.4.17;
contract Fruits {
    string[] public items;
    constructor () public {
        items.push('apple');
        items.push('orange');
        string[] storage newItems = items;
        newItems[1] = 'lemon';
    }
}
// items[1] will now be lemon
// items[0] will remain the same as 'apple'

В заключение, ключ хранения заставляет вновь созданную переменную указывать на переменную состояния (элементы), а не на копию. Любые изменения, внесенные в новую переменную, напрямую изменят структуру переменной состояния контракта.

NewItems как память

newItems имеет альтернативный метод настойчивости. Вместо storage есть вариант memory. Параметр memory действует как копия, а не как указатель. Таким образом, использование ключа memory и внесение изменений во вновь созданную переменную НЕ повлияет на исходную переменную состояния. Пример ниже.

pragma solidity ^0.4.17;
contract Fruits {
    string[] public items;
constructor () public {
        items.push('apple');
        items.push('orange');
        string[] memory newItems = items;
        newItems[1] = 'lemon'
    }
}
// items[1] will remain the same as 'orange'
// items[0] will remain the same as 'apple'

Заключение

Я надеюсь, что это решит любой вопрос относительно этого предупреждения относительно памяти и хранилища. Я рассматриваю хранилище (состояние) как жесткий диск, а память (локальные переменные, временные) как RAM. Если вы планируете создавать функции, которые изменяют состояние, лучшим вариантом может стать хранилище. Если вам нужна копия данных, но не обязательно манипулировать состоянием контракта, используйте память.

Примечание

Любое значение, переданное в функцию контракта, будет отправлено через память (по умолчанию). Функция, которая принимает этот параметр, не будет управлять состоянием контракта. Пример ниже.

pragma solidity ^0.4.17;
contract Fruits {
    string[] public items;
    constructor () public {
        items.push('apple');
        items.push('orange');
        
        changeFirstElement(items);
    }
    
    function changeFirstElement(string[] newItems) pure private {
        newItems[0] = 'lemon';
    }
}
// items[1] will remain the same as 'orange'
// items[0] will remain the same as 'apple'

Если вам нужно принудительно изменить состояние, вы можете добавить в параметр ключ storage.

function changeFirstElement(string[] storage newItems) private {
        newItems[0] = 'lemon';
}

плавник