# Error-Driven Development
It is often said that the compilation time of compiled languages slows down development. It makes sense - any time you make a change and want to see the results, you have to wait for your code to compile. If you were using an interpreted language, you could run it and see the results right away.
But this assumes that your code is correct. Compilers often operate in two phases - a typechecking phase, and a codegen phase. Usually it is the codegen phase that is slow - type checking is very fast.
(It's sometimes said that the reason rust compilation times are slow is because rust does so much static analysis, but the vast majority of the compilation time is actually from codegen.)
And if your code is incorrect in a way that can be detected by the type checker, you can often get a much faster development cycle in a compiled language, because instead of needing to run your code and actually exercise the error (slow), the typechecker can detect it and tell you exactly where the mistake is (fast).
If you have good IDE integration in your language, you often get those red squiggly underlines around areas that need fixing. What feedback loop could be faster than that? You don't even have to go into your terminal, you can see the errors immediately.
This leads to a different mindset towards editing code. It takes some getting used to, but after a while of using a compiled language, you can predict what errors you'll get from a certain change, and then use those errors to navigate around the code.
For example, in Rust, the compiler checks that all `match` statements on enums handle every case. That means that after adding a new variant to an enum, the compiler will notify you of all the `match` statements that need a new case.
That makes adding a new variant to an enum extremely easy. In general, if you know that all of the errors that would result from a change you want to make would be detected by the type checker, it is usually extremely easy to make that change.
## Compounding
This has massive ramifications for your code. Since these changes are easy, you can do them as soon as you feel like it makes your code better. Often these changes improve the architecture of your codebase. Since you do these changes more quickly and more often, you have more cycles of architectural-improvement in the same amount of time.
## Documentation
Another way of looking at types is that they're documentation. For example, imagine a function takes a `NonZeroU64`. That is clearly documentation that the function must not be passed `0` for that argument. While types don't remove the need for explicit documentation, they do provide some documentation that the language forces you to add, can't be ignored, and is always up-to-date.
Suppose you upgrade a version of dependency. A function in the old version of a dependency took a `u64`, while the function in the new version of the dependency takes a `NonZeroU64`. You don't have to read a migration guide or look at a changelog to detect this, and you don't have to poke around your app waiting for a crash. You will get an error the first time you compile, and the error will explain that you passed a `u64` but should have passed a `NonZeroU64`. This makes upgrading dependencies much easier, which makes it easier to keep your dependencies up to date.
(Of course, the best dependency upgrade is the one that introduces no breaking changes at all. But often breaking changes are introduced by accident. Again, types help us here. [`cargo-semver-checks`](https://crates.io/crates/cargo-semver-checks) looks at the type signatures of the old version and the new version of your library, and scans the changes for common sources of breakage. Does anything like this exist for Python? I'm not sure, but I bet it would be much harder to implement.)
## You may also like:
1. [[the-good-and-bad-of-cpp-as-a-rust-dev]]
2. [[whats-wrong-with-a-for-loop]]