Thanks for bringing this up, because typed errors would have an impact on APIs if we get to the place that @beccadax pitches (and I hope we do) in [Pre-Pitch] Import access control: a modest proposal where the default behavior of import
would not leak everything and you would need to use public import
to import anything that you want to use part of the public APIs that you export.
When precise error types aren't encoded as part of the API, you can do something like this:
ModuleA.floop
callsModuleB.blip
and throws anything thatblip
throws- Otherwise,
ModuleB
is purely an implementation detail ofModuleA
; the latter doesn't use any of the former's types in its APIs, soModuleA
importsModuleB
as implementation-only MainModule
importsModuleA
to callfloop
. It can either:- Import
ModuleB
directly if it wants to handle those errors specifically - Ignore
ModuleB
and treat any of its errors as existentials with generic handling
- Import
The benefit of implementation-only imports is that dependency graphs can be pruned, which is very important for build performance of large projects. If MainModule
doesn't care about the specific types of errors that might come from ModuleB
and just wants to handle them generically, it doesn't have to include ModuleB
(and its transitive closure) as a compile-time dependency. Or it can choose to explicitly opt-in to using ModuleB
by depending on it and importing it.
If APIs started adopting typed errors everywhere, then those error types would have to become part of the public API of the module. In the example above, if ModuleA.floop
was declared as throws(ModuleB.BlipError)
then it would have to import ModuleB
as a public import instead of an implementation-only import, thereby "infecting" ModuleA
with the requirement that ModuleB
also be present in the dependency closure passed to the compiler whenever anyone imports it. MainModule
would have no choice but to ensure that ModuleB
was present, whether it actually used it or not.
This can be subtle, but discouraging people from naïvely leaking implementation details into their API when they're really not necessary is critical if we want to move toward a model where public imports are no longer the default.
So I agree with much of the sentiment that I've seen so far in this thread: typed errors have their place in certain situations, but the default should be to not use them and most users should be strongly discouraged from doing so. I'm not sure what the best way for the language/compiler to do that is, though.
It almost feels like there should be a higher barrier to declaring that you throw errors declared in a different module than errors declared in your own. In a future public vs. implementation-only import world, that barrier could be having to change an import
to a public import
(assuming the module wasn't already public import
ed). And maybe that would be enough to force an author to think about that API boundary and its implications.