You can skip type system magic, and have people just keep introducing bugs that it would prevent over and over, writing more tests trying to prevent it, never quite succeeding, but overall "feeling productive" and "being agile" along the way.
Having said that - yeah, making good apis and abstractions that prevent mistakes takes time and some skill, and pays off gradually proportional to the scale and longevity of the project. And for certain things is not worth it. Being able to make a good judgment if enforcing something at the compile time / runtime / at all is worth it is part of the skill.
> You can skip type system magic, and have people just keep introducing bugs that it would prevent over and over, writing more tests trying to prevent it, never quite succeeding, but overall "feeling productive" and "being agile" along the way.
There's a middle ground, and I was specifically responding to the quoted bit: "Its type system is very powerful and allows you to encode complex invariants about your system in the type system."
Once people start "encoding complex invariants in the type system" it becomes an unholy mess only one or two people on the team understand. Usually an ad-hoc unspecified poorly thought-out type-level DSL dozens of level deep.
> You can skip type system magic, and have people just keep introducing bugs that it would prevent over and over
Personally, I found even Haskell's type system easier and nicer to use than fiddling with _lifetimes_ in Rust types. Everything else in Rust is falling nice into place, but lifetimes are painful, magical and very annoying.
Having said that - yeah, making good apis and abstractions that prevent mistakes takes time and some skill, and pays off gradually proportional to the scale and longevity of the project. And for certain things is not worth it. Being able to make a good judgment if enforcing something at the compile time / runtime / at all is worth it is part of the skill.