ethereum/solidity

Introduce alias for variables

axic opened this issue ยท 17 comments

axic commented

An aliased variable works like a macro - the original variable is looked up at every access.

Any example of the alias keyword:

contract A {
    uint alias number = block.number;
    function a() returns (uint) {
        return number; // this returns the *current* block.number every time it is called
    }
}

(Split off #992.)

axic commented

@chriseth I'm not convinced this is a good feature. Can you explain it more where alias is useful?

Having proper macros is a different case.

I'm skeptical too. Maybe there's a better example of where it would be useful?

@axic I'm also not convinced. The idea was to use a much more generic thing, which can bind any identifier to any other identifier:

contract c {
  using a = uint;
  using myType = SomeLibrary.SomeNamespace.SomeType;
  using selfdestruct = suicide;
}

This would also work for block.number, but perhaps not for expressions like using tomorrow = now + 1 day.

axic commented
using a = uint;
using myType = SomeLibrary.SomeNamespace.SomeType;

These two seem more like what a C typedef is, while

using selfdestruct = suicide;

is just an alias (or a dumb macro).

axic commented

using a = uint;

I think typedefing primitives should be disallowed. It creates a maze of code for no benefit.

@axic I'm not so sure about this. Of course, every feature can be abused, but it can also allow you to create new types on the ground of existing types:

using counter = uint;
library L {
  function increment(counter c) returns (counter) { return counter + 1; }
}
using L for counter;

contract C {
  function f() {
    counter x = 2;
    x.increment();
    uint y = 3;
    y.increment(); // does not work
  }
}

For establishing invariants I look for places where a storage content is changed. Say balance[]. Usually I just grep the whole source with balance and I find places where it is changed. With aliases this becomes one-step harder because the array content can change for assignments into aliases.

axic commented

@chriseth that seems to be a nice use case. I still fear it will be grossly misused - if only there would be a way to prevent that a bit more.

using is probably misleading, alias or define seem a bit more fitting.

I want readonly aliases, something like uint view alias number = some_struct.member, or even, make it readonly by default.

my 2 cents:

    uint256 stayDayNum = (now.sub(payment[msg.sender][_siteAddress][_orderId].paymentDate)).div(86400);
    uint256 _value = payment[msg.sender][_siteAddress][_orderId].sellerValue;
    address currency = payment[msg.sender][_siteAddress][_orderId].currency;
    address payable buyerAddress = payment[msg.sender][_siteAddress][_orderId].buyerAddress;

Because of the stack size limit I can not do something like this:

    MyStruct s = payment[msg.sender][_siteAddress][_orderId];
    uint256 stayDayNum = (now.sub(s.paymentDate)).div(86400);
    uint256 _value = s.sellerValue;
    address currency = s.currency;
    address payable buyerAddress = s.buyerAddress;

Is not it the perfect usecase for alias/macro/using? I would be happy for any construct that would help me get rid of such ugly code without stack limit problem downside.

I do not fully understand - why would you want to have another local variable like currency if you can use s.currency?

This feature scares me and seems to make code audit more difficult since new side effects are introduced.

@fulldecent I agree.

axic commented

Since the typedef aspect has been discussed in #1100 and #11531, and because the consensus seems to be that the raw "alias" feature is bad, I think this should be closed.

I'm not sure this is on point, but for generic code like this:

function (uint myParam) public pure returns (uint) {
    uint param2 = someFunction(myParam);
    return someOtherFunction(param2);
}

It costs less gas, because it uses less memory to do something like this:

function (uint myParam) public pure returns (uint) {
    myParam = someFunction(myParam);
    return someOtherFunction(myParam);
}

However, there are cases where this could be much more difficult to read: you cannot assign the variable you are working with a meaningful name other than the one it previously had. Thus, if you want to reuse this variable, you have to keep the same name in order not to use another memory slot.

What I had in mind was something like this:

function (uint myParam) public pure returns (uint) {
    uint alias param2 = someFunction(myParam);
    return someOtherFunction(param2);
}

where, at compile time, every instance of param2 is replaced by myParam. It would thus allow to save some gas and to improve code readability. Furthermore, if, after an alias a = b, the compiler does not authorize the use of b anymore, I don't see any side-effect that would occur.

I don't know whether that's possible, nor whether you had this case in mind when discussing this idea. If this is not on point, please tell me and I would be happy to delete my comment and open an appropriate issue ๐Ÿ˜„

Reusing variables to reduce gas costs requires the userland programmer to make assumptions about how the compiler works.

As a userland programmer, don't make assumptions about how the compiler works.


Now if you want to be a solc developer then do this:

  1. Create a minimally viable program using both syntaxes
  2. Profile them
  3. Open an issue stating that the optimizer has an opportunity to improve

Right, if the more readable version produces suboptimal code, please just open a separate issue and we'll see what we can do. We'd rather improve the optimizer than add new syntax just to work around its deficiencies. Also note that the upcoming Yul-based codegen has a much more powerful optimizer and that generator is going to become the default in the near future. You can pass the --experimental-via-ir option to solc to use it - before submitting an issue please make sure that the new optimizer doesn't already handle it better.