Good news. It compiles with a few tweaks.
Bad news. This is hard on gas and depending on the length of the arrays being passed around it may be too expensive to be practical - standard caution whenever arrays are involved in the interface.
As an alternative, you could create getters for the scalar values (return (p.a, p.b, p.d)
) and individual rows in the array (return p.c[row];
). Doing so will ensure the individual invocations will cost the same and conserve gas.
For your code to compile, the trick is to cast c[]
as a memory
pointer. That corresponds to the data received from the other contract via the ABI (i.e. not from storage). By default, it was a storage pointer.
//FileOne.sol
contract ExampleOne {
struct Problem {
bytes32 a; // (1)
bytes32 b;
bytes32[] c;
uint d;
}
mapping (bytes32 => Problem) public problems;
}
//FileTwo.sol
contract IExampleOne {
function problems(bytes32) public returns (bytes32, bytes32, bytes32[], uint);
}
contract ExampleTwo {
IExampleOne exampleOne;
constructor(address _exampleOne) {
exampleOne = IExampleOne(_exampleOne);
}
function func(bytes32 name) {
(bytes32 a, bytes32 b, bytes32[] memory c, uint d) = exampleOne.problems(name); // NO ERROR
//...
}
}
In case the comments about scalability are of interest, the concern is it will take increasing amounts of gas on both sides to pack and unpack larger and larger lists. Your ExampleOne
can offer the potential to read it at any scale using many tiny calls instead of one big one.
function getProblemMeta(bytes32 problemId) public view returns(bytes32, bytes32, uint) {
p = problems[problemId];
return (p.a, p.b, p.d);
}
function getProblemCAtIndex(bytes32 problemId, uint index) public view returns(bytes32) {
return problems[problemId].c[index];
}
Hope it helps.