Skip to content

Writing Signatures Best Practices

Soutaro Matsumoto edited this page Dec 30, 2019 · 1 revision

Types

Prefer void over untyped

void means the value returned from a method/block is expected to be discarded. For example, if the method does not expect to return something, the signature should be () -> void.

def save!
  record.save_or_raise_exception         # The method uses the return value for nothing.
  record.reload                          # #reload returns self, but there is no reason/intention to do so.
end

So is it on block types.

class Foo
  def each: () { (foo) -> void } -> self
end

Prefer void over bot

bot means it returns nothing. We recommend not writing bot.

Prefer nil over NilClass

NilClass means an instance of NilClass. We recommend using nil for more precise type checks.

Prefer bool over true | false

bool is different from true | false. It allows to be any type to be compatible with Ruby's truth value semantics.

If you intentionally require the value to be true or false, you can write true | false. We believe it happens not very often. And provably it mostly appears in co-variant (out) positions. (When you are writing true | false in contra-variant (in) positions, it might be refactored.)

See also: https://github.com/ruby/ruby-signature/issues/133 (no conclusion yet.)

Add general cases for type level computation optimizations using literal types

RBS syntax allows to do type level computation using literal types (true, :symbol, "string", 123). Because true & nil and true & false only returns false and other cases always returns true, we may want to write it in signatures.

class TrueClass
  def &: (nil) -> false
       | (false) -> false
       | (untyped) -> true
end

We don't recommend writing these. For example, some type checkers, Steep at least, cannot handle this case. It may return true for a value of type String?, which may be false.

So, the general advice is:

  • If you write type level optimization using literal types, add base cases using instance types (or equivalent) too.
class TrueClass
  def &: (nil) -> false
       | (false) -> false
       | (untyped) -> bool
end