13

I am currently working with solc v0.4.25, where it is not possible to return structures, so the only way to return multiple values is in a tuple.

I've been advised to give names to each one of the returned values, in order to make the code easier to read and less prone to errors.

However, it would ultimately force me to give names to every return value in my system, even when only a single value is returned.

In order to clarify the above, here are two examples:

function withoutNames(uint x) pure returns (uint, uint) {
    uint y = 2 * x;
    uint z = 3 * x;
    return (y, z);
}

function withNames(uint x) pure returns (uint y, uint z) {
    y = 2 * x;
    z = 3 * x;
}

My question:

Is there an official coding convention (or an accepted standard among the community) for how to deal with return-value naming?

The way I see it, the options are:

  1. Do not name any return value
  2. Name every return value in every function
  3. Name every return value in every function which returns more than one value

Thank you very much for your suggestions.

2 Answers 2

7

Short answer:

The "returned byte buffer" of both methods is exactly the same.

The only difference is the generated contract ABI JSON file which can be used by "external" contract callers ( js / php / python / etc ) to assign variable name to our method's return automatically.

Ie:

   var callResultWithNames = await contract.methods.withNames(1).call();
   var y = callResultWithNames.y;
   var z = callResultWithNames.z;

   // array indexing also works
   // var y = callResultWithNames[0];
   // var z = callResultWithNames[1];

Versus

   var callResultWithoutNames = await contract.methods.withoutNames(1).call();
   var y = callResultWithoutNames[0];
   var z = callResultWithoutNames[1];

Detailed answer:

Web3 calls

function withoutNames(uint x) public pure returns (uint, uint);

ABI JSON ->
    "name": "withoutNames",
    "outputs": [
        {
            "name": "",       <<<<
            "type": "uint256"
        },
        {
            "name": "",       <<<<
            "type": "uint256"
        }
    ],
function withNames(uint x) public pure returns (uint y, uint z);

ABI JSON ->
    "name": "withNames",
    "outputs": [
        {
            "name": "y",      <<<<
            "type": "uint256"
        },
        {
            "name": "z",      <<<<
            "type": "uint256"
        }
    ],

Solidity calls

When talking about calling such a method from within a contract directly in solidity code, we're already assigning the returned values to variables ( a ) or reading the returned buffer through assembly using a staticcall ( b ), in which case we don't need the variable names.

a) in code

var x,y = contract.withoutNames(1);

b) in assembly

    assembly {
        let success := staticcall(
                                500000,             // Gas to use for this call
                                toAddress ,         // To addr
                                callBufferPointer,  // Inputs are stored at current ptr location
                                callDataLength,     // input length
                                0,          
                                0)          
        // copy result byte size from return value ( ignore first 32 bytes ) ( next 32 bytes )
        returndatacopy( 
            thisOutputPointer,
            0, 
            returndatasize() 
        )
        // manipulate and use the returned data stored at thisOutputPointer address any way we want.
    }

Notes

Even thou having the names of the returned values in the function declaration result in automatic generation of these objects when loaded by web3, I personally find it very distracting when reading the code step by step and usually prefer my return statements at the end of a method.

3
  • This is an extremely informative and very well-phrased answer, which refers to various interesting aspects of the issue at hand, namely runtime aspects. So I appreciate it very much. However, it doesn't quite refer to the aspect of coding-convention, which is why I asked this question to begin with. P.S.: It did remind me that web3.js returns a tuple as a JSON object with incrementing keys (i.e., '0', '1', ...). And if the return values are named, then it uses their names as keys, which is very useful for an off-chain user (or tests). So thanks for reminding me of this. Feb 27, 2019 at 14:40
  • There is no coding standard regarding these as far as i know, mostly because they're not really used by solidity internally, and the "code reading" confusion i was referring to. We could start one, thou i already see the community being divided in the "automatically generate return names" vs "keep returns at the end for better readability" groups :) Feb 27, 2019 at 14:54
  • 1
    Also at the time of writing this, most if not all examples in the official documentation as well as the styling guide adhere to the "return variables at the end of the method" coding style. Feb 27, 2019 at 15:00
5

From the point of view of solidity code the use of named return values is choosed when:

1) it increases by far the readability. For instance any function defined as

function foo( ... ) returns (bool success)

is immediately understood. The alternative:

function foo( ... ) returns (bool) // true if successful

is by far less readable.

2) BY FAR THE MOST IMPORTANT when you plan to use local variables in order to optimize gas in calculations which implies state variable.

If you need to have this function:

function foo( ... ) returns (uint) {
return(globalPubliCVariable*globalPubliCVariable*globalPubliCVariable + 2*globalPubliCVariable + 1);
}

it is widely known that you can have big advantages in terms of gas cost using a local temporary variable:

function foo( ... ) returns (uint) {
uint res;

res = globalPublicVariable;
res = res*res*res+ 2*res + 1;

return(res);
}

but you can have further advantages in term of gas cost simply using named return values as temporary local variable:

function foo( ... ) returns (uint res) {
res = globalPublicVariable;
res = res*res*res+ 2*res + 1;
}

where the difference is that you did not create any local variable for intermediate results. (It can be demonstrated that, this way, you minimize the stack deep).

In all the other cases you can use the named return or the unnamed without differentiations.

———————————

EDIT #1 to address various comments

to better explain second point, you need to know that when a function is called, the caller push on the stack the parameters, the space for the return values, the return address (not in this order). At this level it is exactly the same to have named or unnamed return values. When the control to the called function is passed, if the called function has declared some local variables, they are pushed on the stack as well. This means that if you DO NOT have such local variables your stack is smaller than that in the case you have them. Given this, sometimes you can avoid to declare new local variables used to store intermediate results USING temporary named returns value (they already exist and shall be written when a return is encountered only... if they are unnamed. If they, on the contrary, are named... they can be used in that called routine as local variables and the last value written in them before returning shall be the return value readed by the caller. So: you can write and read whatever you want before return. Without using additional stack space. Of course you must be sure that, when returning, you shall have written the proper return values in it. Just before returning. .

EDIT #2 - important but wrong comment

“I think you've missed MY point. Optimizing stack size can be done regardless of whether your local variable is declared in the header of the function or in the body of the function. For every example of a function which declares the return variable in the header, one can implement an equivalent function which declares the return variable in the body. These two implementations differ only in the syntax, NOT in their compiled code, hence not in their runtime behavior. – goodvibration”

This is absolutely wrong. In any (ANY) situation the stack deep during the execution of any called function is:

RLSD = sizeof(function parameters) + sizeof(return address) + sizeof(return values) + sizeof(local temporary variables in the called function)

It is IMPOSSIBLE to have it lower than the RLSD of the case where the latter is zeroed. Full stop.

The allocation of temporary variables is not a syntax sugar: it is a deep structural choice. And you can (sometimes) avoid it REUSING the space reserved for return values (it is there ANYWAY) as a temporary buffer in the called function execution. And you can do this choice, if and only, your return values are “named”.

1
  • Comments are not for extended discussion; this conversation has been moved to chat.
    – eth
    Feb 28, 2019 at 10:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.