This compilation error is encountered when you try to instantiate a struct that contains a mapping as an attribute. I needed this data structure to write this contract to allow patients to license the datasets resulting from their clinical trials, as part of an Algovera grant.
This type of one-to-many data structure can be useful, for instance, to encode a marketplace for time-limited licenses to some content. In addition to the content to be licensed, the struct would need to map licensees' addresses with a timestamp for the start of their license, so that their access can be controlled.
struct content{
address payable owner;
bytes32 hash;
mapping(address => uint) licenses;
}
Visually it would look like this:
The "struct containing a (nested) mapping cannot be constructed" error would appear when trying to instantiate the struct, the usual way, with a recent compiler version.
Like this, for instance:
function submitContent(bytes32 _hash) public payable returns(uint _id) {
_id=contents[msg.sender].length;
contents[msg.sender].push(content({
owner: payable(msg.sender),
hash:_hash return(_id);
}));
}
Indeed, since version 0.7.0, structs or arrays that contain a mapping can only be used in storage, so Solidity complains because variables in an instantiation function would be in memory by default. You can read about this change in the Solidity documentaiton.
The relevant part is under "Removal of Unused or Unsafe Features":
I wanted to write this short note because some top results when Googling this error are either outdated, or to the extent of "just don't use a mapping in a struct bro".
However, this type of structure cannot be equivalently modeled with a separate mapping for licenses; reading and writing would require much more computation.
The workaround is quite simple, we just have to declare the struct in storage before we instantiate a pointer to it.
Our submitContent() function would have to look like this:
function submitContent(bytes32 _hash) public payable returns(uint _id) {
_id= contents[msg.sender].length;
content[] storage c = contents[msg.sender];
c.push();
c[_id].owner = payable(msg.sender);
c[_id].hash=_hash;
return _id;
}