6 ms·
You can't guarantee the order of registration will always be the same, so it's really undefined behavior. This is how the original ServeMux was designed and imp
by numbsafari 3y ago
You can't guarantee the order of registration will always be the same, so it's really undefined behavior. This is how the original ServeMux was designed and implemented, and they felt that was useful behavior to continue to support.
From the design proposal[1]:
> Using specificity for matching is easy to describe and
> preserves the order-independence of the original ServeMux
> patterns. But it can be hard to see at a glance which of
> two patterns is the more specific, or why two patterns
> conflict. For that reason, the panic messages that are
> generated when conflicting patterns are registered will
> demonstrate the conflict by providing example paths, as in
> the previous paragraph.
See also the background discussion[2]:
> The semantics of the mux do not depend on the order of the
> Handle or HandleFunc calls. Being order-independent makes
> it not matter what order packages are initialized (for
> init-time registrations) and allows easier refactoring of
> code. This is why the tie breakers are not based on
> registration order and why duplicate registrations panic
> (only order could possibly distinguish them). It remains a
> key design goal to avoid any semantics that depend on
> registration order.
[1]: https://github.com/golang/go/issues/61410 https://github.com/golang/go/issues/61410
[2]: https://github.com/golang/go/discussions/60227 https://github.com/golang/go/discussions/60227
- iudqnolq 3y agoThis seems like an issue caused by registration at a distance. I'm new to go and it is one of the things that felt most wrong. I'm used to a router where you define a big list of routes all in one place, maybe in some DSL. That's very easy to refactor, and doesn't have any ambiguity. If a library wants a route registered it puts a snippet in the docs for users to copy. Go in general seems to value clarity over magic at the expense of verbosity, and this is a puzzling exception.
- numbsafari 3y agoIt’s not an exception. There is no magic. Large code bases are large, and not every program is your simple crud app with a handful of endpoints that can be meaningfully managed a single function.
- troupo 3y agoHard panic in a "not simple app with large codebase" app because you couldn't match a route is amateur hour.
- numbsafari 3y agoI think you are vastly oversimplifying what has been said or why it works the way that it does. I really recommend you read the design document and reasoning, as it is rather clear why they are doing it the way that they are. If you have a cogent contribution to the discussion, please do share.
- troupo 3y agoOthers have already contributed more than enough. People's willingness to defend any and all of go's dubious decisions is really baffling.
- msmith 3y agoI think you're assuming that the panic happens when a request is received, but it actually happens when a conflicting route is registered. To me, that's reasonable behavior and is consistent with other things such as https://pkg.go.dev/regexp#MustCompile https://pkg.go.dev/regexp#MustCompile
- smoyer 3y agoRegistering handlers is a startup activity ... Practically the only time I allow a panic in my code. I also have unit tests for the entire startup sequence so theoretically code with ambiguous handlers would never be committed.
- earthboundkid 3y agoGo's flag, log, image, and sql packages all work this way, so it's not just the HTTP router. I sort of see both sides. Having implicit registration makes it very easy to have different teams working on different packages and you just import the package and it registers itself. But it also makes the behavior of the resulting final binary hard to understand and based on implicit code instead of explicit. I personally try to just have one big routes() http.Handler function that returns everything all in one place, but I get why that isn't always practical.
- silasdavis 3y agoIt doesn't feel to me like much of a gain over having said team expose a specific default self registration function. Then that act can be explicit rather than action at a distance on some dependency defined global state. I think this pattern in the standard library is a mistake.
- Groxx 3y agoYeah, the trade-off is literally: import _ "thing" Vs: import "thing" thing.Register() But one uses a strange construct to save a single line, loses the ability to control order, and encourages people to use globals that they can't control.
- cowl 3y agothing.Register() may register a route that conflicts with yours and it will never be matched in this case if declaration order was taken into account. You may discover this too late when you either have missed important calls on that thing or when requests intended for that thing are causing unintended effects on your first declared route.
- Groxx 3y agoThat is all also true when doing the same thing in an init func.
- 3y ago
- cowl 3y agoHow is this new to Go? Almost all Java frameworks that rely on Annotations do registration at a distance. Controllers are Routinely declared on different packages and even injected from dependency libraries.
- throwaway2037 3y agoI agree with most of this post. The one exception in my experience: Vertx. I never saw any annotations for routing. It is a very verbose library, but that means there is no black magic.