Overview

Iron is a type constraints (or "refined types") library for Scala. It enables binding constraints to a specific type. This processus is called "type refinement".

Why refined types matter

In production code bases, it is important to make sure that all values passed are valid or handled correctly. Static typing like in Scala is useful to avoid using such issue. However, despite strong static typing, some invalid values still type check.

Taking for example a User with an age:

case class User(age: Int)
User.scala

Thanks to static typing, we cannot pass values of a different type:

case class User(age: Int)

User(1) //OK
case class User(age: Int)

User("1") //Error

However, unexpected values can still be passed because Int contains "wrong" values for our use case:

case class User(age: Int)

User(-1) //A User with a negative age?

To fix this caveat, you have to write an assertion/guard condition with the following drawbacks:

  • You have make sure your value is verified wherever you use it
  • The assertion is only evaluated at runtime: bugs/wrong values may not be detected

Refined types solve both problems by ensuring that constraints are checked compile time or explicitly at runtime.

import io.github.iltotore.iron.*
import io.github.iltotore.iron.constraint.numeric.*

case class User(age: Int :| Positive)
BetterUser.scala
import io.github.iltotore.iron.*
import io.github.iltotore.iron.constraint.numeric.*

case class User(age: Int :| Positive)

User(1) //Compiles
User(-1) //Does not compile
User(-1.refineUnsafe) //Compiles but fails at runtime. Useful for runtime checks such as form validation.
//See also `refineOption` and `refineEither`

Use cases

Iron and refined types in general are useful in many cases:

  • Data validation (form, API...)
  • Securing business data types
  • Mathematics

Getting started

See the Getting started page to set up and start using Iron.

See references for details about the concepts of Iron.