Using directive

Binding methods to types with using

Methods can be bound to builtin types and any user-defined types like structs using the using syntax. This can be done either using libraries or free standing functions.

using with free standing functions

First, declare a function with one or more arguments. Once the function is bound with using, it can be called like a method.

function mask(uint v, uint bits) returns (uint) {
    return v & ((1 << bits) - 1);
}

function odd(uint v) returns (bool) {
    return (v & 1) != 0;
}

contract c {
    using {mask, odd} for *;

    uint v;

    function set_v(uint n) public {
        v = n.mask(16);
    }
}

The using declaration can be done on file scope. In this case, the type must be specified in place of *. The first argument must match the type that is be used in the using declaration.

If a user-defined type is used, the global keyword can be used. This means the using binding can be used in any file, even when the type is imported.

struct User {
    string name;
    uint count;
}

function clear_count(User memory user) {
    user.count = 0;
}

using {clear_count} for User global;

Now even when User is imported, the clear_count() method can be used.

import {User} from "./user.sol";

contract c {
    function foo(User memory user) public {
        user.clear_count();
    }
}

User defined Operators

The using directive can be used to bind operators for user defined types to functions. A binding can be set for the operators: ==, !=, >=, >, <=, <, ~, &, |, ^, - (both negate and subtract), +, *, /, and %.

First, declare a function with the correct prototype that implements the operator.

  • The function must be free standing: declared outside a contract.

  • The function must have pure mutability.

  • All the parameters must be the same user type.

  • The number of arguments depends on which operator is implemented; binary operators require two and unary operators, one.

  • The function must return either bool for the comparison operators, or the same user type as the parameters for the other operators.

Then, bind the function to the operator using the syntax using {function-name as operator} for user-type global;. Operators can only be defined with global set. Note that the - operator is used for two operators: subtract and negate. In order to bind the unary negate operator, the function must have a single parameter. For the subtract operator, two parameters are required.

type Bitmap is int256;

function sub(Bitmap a, Bitmap b) pure returns (Bitmap) {
        return Bitmap.wrap(Bitmap.unwrap(a) - Bitmap.unwrap(b));
}

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

function neg(Bitmap a) pure returns (Bitmap) {
        return Bitmap.wrap(-Bitmap.unwrap(a));
}

using {sub as -, neg as -, add as +} for Bitmap global;

function foo(Bitmap a, Bitmap b) {
        Bitmap c = a + b;
        // ...
}

using with libraries

A library may be used for handling methods on a type. First, declare a library with all the methods you want for a type, and then bind the library to the type with using.

struct User {
    string name;
    uint name;
}

library UserLibrary {
    function clear_count(User user) internal {
        user.count = 0;
    }

    function inc(User user) internal {
        user.count++;
    }

    function dec(User user) internal {
        require(user.count > 0);
        user.count--;
    }
}

using UserLibrary for User global;

Scope for using

The using declaration may be scoped in various ways:

  • Globally by adding the global keyword. This means the methods are available in any file.

  • Per file, by omitting the global keyword

  • Per contract, by putting the using declaration in a contract definition

If the scope is per contract, then the type maybe be replaced with * and the type from the first argument of the function will be used.