5.3.2. Version Constraints and Pinning
💡 First Principle: A version constraint encodes your risk tolerance — exact pins favor stability, ~> favors automatic patch/minor updates within a safe range — so choosing the operator is really choosing how much change you'll accept without review.
Version constraints let you balance stability against staying current:
| Constraint | Allows | Use when |
|---|---|---|
= 5.1.2 (or 5.1.2) | Only that exact version | Maximum stability, production |
~> 5.1 | 5.1 and later 5.x, not 6.0 | Accept minor/patch within major |
~> 5.1.0 | 5.1.x patches only | Accept patches, not new minors |
>= 5.0, < 6.0 | Explicit range | Custom bounds |
Pinning matters because module authors can change inputs, outputs, or behavior between versions. An unpinned constraint plus init -upgrade can pull a breaking change. The reproducibility safeguard you met for providers — the lock file — also records selected module-registry versions, reinforcing why pinning and committing the lock file go together.
⚠️ Exam Trap: As with providers, ~> 5.1 (two segments) allows any 5.x at or above 5.1, while ~> 5.1.0 (three segments) restricts to 5.1.x patches. The segment count determines what's allowed to increment — read it carefully.
Reflection Question: Your module uses ~> 5.0 and a teammate's init -upgrade pulled 5.9, which broke a variable name. What constraint would have prevented this, and what's the trade-off?