Pattern matching doubles as native type-hinting (ie, not bolted on--it's always been there). For example:
def foo(%{} = map) do
map
end
This will crash if `map` is anything other than a map.
There are also guards (which is another form of pattern matching):
def foo(map) when is_map(map) do
map
end
Again, this will crash if given anything other than a map.
It isn't always necessary to do this since Elixir doesn't overload operators or many functions that work on types (although it does have the `Enum` module which works across collection types).
For example, `+` only works on numbers. So if you have:
def add(a, b) do
a + b
end
You aren't going to run into those "subtle bugs" people talk about, ie, if you give it anything other than two numbers (like two strings or two lists) it will crash right there. These are only runtime checks but this is where a good set of sane tests help as there is no need to write any type-checking tests.
As mentioned by others, as of a couple of weeks ago the latest 1.7 release candidate has the beginnings of the type system. A big wart has been comparison operators as they will silently compare any two types. This has been half-fixed in 1.17 insofar as a compiler warning will be thrown if you compare two different types. Of course you could still run into the "subtle bug" issue, but this is where pattern matching and guards can help.
Until the type system is finished there is always still Dialyzer if you're into that! It will likely be a while before the type system is fully ready as they are really taking their time with it to do it right.