Support an alternative to `match` in Rust

@openzeppelin/contracts/utils/cryptography/ECDSA.sol line 23

    function _throwError(RecoverError error) private pure {
        if (error == RecoverError.NoError) {
            return; // no error: do nothing
        } else if (error == RecoverError.InvalidSignature) {
            revert("ECDSA: invalid signature");
        } else if (error == RecoverError.InvalidSignatureLength) {
            revert("ECDSA: invalid signature length");
        } else if (error == RecoverError.InvalidSignatureS) {
            revert("ECDSA: invalid signature 's' value");
        }
    }

Found this code on OpenZeppelin’s repo. I suppose how it could be better?. Apparently, conditional logic can be handled very well and effectively using match in Rust. As opposed to a sequence of if/else statement blocks, it compares data to patterns considerably more rapidly, and the compiler does a statically verified version of it, allowing you to catch many errors at build time before they affect your code. Additionally, it makes it simple to add additional match weapons as your code develops, which is excellent for maintainability.

2 Likes

We are actually planning to add something like that when we implement enums with data. And these are coming sooner or later. Our roadmap for this year is full, but it has a very high chance of being one of the milestones after that.

Until then, there are some design questions that need to be answered. It would be very interesting to see what are people’s opinions on these:

  • Should it be an expression or a statement?
  • Syntax for destructuring of tuples, structs and enums with data.
  • Do we need match guards?
  • Do we need support for integer ranges? What syntax?
1 Like
  • Should it be an expression or a statement?
    An expression would be better for matching pattern, destructing enum. It let me rewrite above code much more clear.
function _throwError(RecoverError error) private pure {
    string memory errorMessage = match error {
        RecoverError.InvalidSignature = > "ECDSA: invalid signature",
        RecoverError. InvalidSignatureLength = > "ECDSA: invalid signature length",
        RecoverError.InvalidSignature = > "ECDSA: invalid signature 's' value",
        RecoverError. NoError = > {
            return;
        },
    }
    revert(errorMessage);
}

Syntax for destructuring of tuples, structs and enums with data.

  • Destructuring tuples:
match <Tuple> {
    (Pattern) => (BlockExpression | Expression)
}

E.g:

match tuple {
    (address actor) => _permissionOf(actor, permission),
    (address owner, address actor) => _delegated(owner, actor)
}
  • Destructuring structure:
match <Structure> {
    Structure{Pattern} => (BlockExpression | Expression)
}

E.g:

match structure {
    Proposal {proposalId: uint256, voted: uint256} => {}
}
  • Destructuring enum:
match <Enum> {
    Enum(Pattern) => (BlockExpression | Expression)
}

E.g:

match enum {
    Ok(uint256 result) => {},
    Err(uint256 errorCode) => {}
}

Do we need match guards?

Yes, we do. It helps developer to prevent vulnerabilities.

Do we need support for integer ranges? What syntax?

The explicit conversion from integer checks at runtime that the value lies inside the range of the enum and causes a Panic error otherwise

I’ve found this in document. It would be helpful if we want to match a integer value back to enum.

E.g:

enum Permission {
    None = 0;
    Read = 1;
    Write = 2;
    Execute = 4;
}

match permissionInt {
    1 => Permission.Read,
    2 => Permission.Write,
    4 => Permission.Execute,
    _ => Permission.None
}