Skip to content

Latest commit

 

History

History
236 lines (195 loc) · 9.18 KB

using-for.rst

File metadata and controls

236 lines (195 loc) · 9.18 KB
.. index:: ! using for, library, ! operator;user-defined, function;free

Using For

The directive using A for B can be used to attach functions (A) as operators to user-defined value types or as member functions to any type (B). The member functions receive the object they are called on as their first parameter (like the self variable in Python). The operator functions receive operands as parameters.

It is valid either at file level or inside a contract, at contract level.

The first part, A, can be one of:

  • A list of functions, optionally with an operator name assigned (e.g. using {f, g as +, h, L.t} for uint). If no operator is specified, the function can be either a library function or a free function and is attached to the type as a member function. Otherwise it must be a free function and it becomes the definition of that operator on the type.
  • The name of a library (e.g. using L for uint) - all non-private functions of the library are attached to the type as member functions

At file level, the second part, B, has to be an explicit type (without data location specifier). Inside contracts, you can also use * in place of the type (e.g. using L for *;), which has the effect that all functions of the library L are attached to all types.

If you specify a library, all non-private functions in the library get attached, even those where the type of the first parameter does not match the type of the object. The type is checked at the point the function is called and function overload resolution is performed.

If you use a list of functions (e.g. using {f, g, h, L.t} for uint), then the type (uint) has to be implicitly convertible to the first parameter of each of these functions. This check is performed even if none of these functions are called. Note that private library functions can only be specified when using for is inside a library.

If you define an operator (e.g. using {f as +} for T), then the type (T) must be a :ref:`user-defined value type <user-defined-value-types>` and the definition must be a pure function. Operator definitions must be global. The following operators can be defined this way:

Category Operator Possible signatures
Bitwise & function (T, T) pure returns (T)
| function (T, T) pure returns (T)
^ function (T, T) pure returns (T)
~ function (T) pure returns (T)
Arithmetic + function (T, T) pure returns (T)
- function (T, T) pure returns (T)
function (T) pure returns (T)
* function (T, T) pure returns (T)
/ function (T, T) pure returns (T)
% function (T, T) pure returns (T)
Comparison == function (T, T) pure returns (bool)
!= function (T, T) pure returns (bool)
< function (T, T) pure returns (bool)
<= function (T, T) pure returns (bool)
> function (T, T) pure returns (bool)
>= function (T, T) pure returns (bool)

Note that unary and binary - need separate definitions. The compiler will choose the right definition based on how the operator is invoked.

The using A for B; directive is active only within the current scope (either the contract or the current module/source unit), including within all of its functions, and has no effect outside of the contract or module in which it is used.

When the directive is used at file level and applied to a user-defined type which was defined at file level in the same file, the word global can be added at the end. This will have the effect that the functions and operators are attached to the type everywhere the type is available (including other files), not only in the scope of the using statement.

Let us rewrite the set example from the :ref:`libraries` section in this way, using file-level functions instead of library functions.

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.13;

struct Data { mapping(uint => bool) flags; }
// Now we attach functions to the type.
// The attached functions can be used throughout the rest of the module.
// If you import the module, you have to
// repeat the using directive there, for example as
//   import "flags.sol" as Flags;
//   using {Flags.insert, Flags.remove, Flags.contains}
//     for Flags.Data;
using {insert, remove, contains} for Data;

function insert(Data storage self, uint value)
    returns (bool)
{
    if (self.flags[value])
        return false; // already there
    self.flags[value] = true;
    return true;
}

function remove(Data storage self, uint value)
    returns (bool)
{
    if (!self.flags[value])
        return false; // not there
    self.flags[value] = false;
    return true;
}

function contains(Data storage self, uint value)
    view
    returns (bool)
{
    return self.flags[value];
}


contract C {
    Data knownValues;

    function register(uint value) public {
        // Here, all variables of type Data have
        // corresponding member functions.
        // The following function call is identical to
        // `Set.insert(knownValues, value)`
        require(knownValues.insert(value));
    }
}

It is also possible to extend built-in types in that way. In this example, we will use a library.

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.13;

library Search {
    function indexOf(uint[] storage self, uint value)
        public
        view
        returns (uint)
    {
        for (uint i = 0; i < self.length; i++)
            if (self[i] == value) return i;
        return type(uint).max;
    }
}
using Search for uint[];

contract C {
    uint[] data;

    function append(uint value) public {
        data.push(value);
    }

    function replace(uint from, uint to) public {
        // This performs the library function call
        uint index = data.indexOf(from);
        if (index == type(uint).max)
            data.push(to);
        else
            data[index] = to;
    }
}

Note that all external library calls are actual EVM function calls. This means that if you pass memory or value types, a copy will be performed, even in case of the self variable. The only situation where no copy will be performed is when storage reference variables are used or when internal library functions are called.

Another example shows how to define a custom operator for a user-defined type:

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.19;

type UFixed16x2 is uint16;

using {
    add as +,
    div as /
} for UFixed16x2 global;

uint32 constant SCALE = 100;

function add(UFixed16x2 a, UFixed16x2 b) pure returns (UFixed16x2) {
    return UFixed16x2.wrap(UFixed16x2.unwrap(a) + UFixed16x2.unwrap(b));
}

function div(UFixed16x2 a, UFixed16x2 b) pure returns (UFixed16x2) {
    uint32 a32 = UFixed16x2.unwrap(a);
    uint32 b32 = UFixed16x2.unwrap(b);
    uint32 result32 = a32 * SCALE / b32;
    require(result32 <= type(uint16).max, "Divide overflow");
    return UFixed16x2.wrap(uint16(a32 * SCALE / b32));
}

contract Math {
    function avg(UFixed16x2 a, UFixed16x2 b) public pure returns (UFixed16x2) {
        return (a + b) / UFixed16x2.wrap(200);
    }
}