Skip to content
This repository was archived by the owner on May 25, 2023. It is now read-only.

Why do IPPrefixFrom and IPRangeFrom permit constructing invalid values? #178

Closed
danderson opened this issue May 21, 2021 · 7 comments
Closed

Comments

@danderson
Copy link
Member

IPPrefix and IPRange have a Valid method. This was very necessary when the types were transparent, since you could put whatever you wanted in them.

Now that the types are opaque, should their constructors return errors? Then we can guarantee that a particular IPPrefix/IPRange is always valid or zero, rather than have to deal with the possibility of invalid inputs into other functions.

If yes, should we also add Must* helpers that panic on error?

I'm intrigued by the idea of making invalid values unrepresentable, but also worried that it makes the API unwieldy to manipulate. thoughts?

@bradfitz
Copy link
Contributor

You can still make a zero value that's invalid, even with an opaque type, e.g. https://play.golang.org/p/kAegzb_KbyG ....

func main() {
	v := reflect.Value{}
	fmt.Println(v.IsValid()) // false
}

The Must idiom is Go is generally reserved for things that will be made in init as package globals (regexp.MustCompile, template.Must). I'm wary of adding ones that are tempting to use in normal program control flow and panic at runtime rather than at init time. These might be okay, but IPSet probably isn't (as you can't use IPSet in a single expression anyway, so if you're doing it at init, it's via a func)

Speaking of reflect, though, that's the other style we could imitate: let you make invalid values, give you an IsValid accessor, but panic on misuse. The reflect package panics like crazy on misuse (I see 86 references to "panic" in its godoc). Arguably it's a low-level weirdo package, and maybe not best to imitate, but it still might be the best option, rather than polluting constructor call sites with error checks (that are usually fine to do without) and without slightly unidiomatic Must prefixes everywhere.

@josharian
Copy link
Collaborator

josharian commented May 21, 2021

I'd much rather have a sleek, easy-to-use API with IsValid and documented panics than an awkward and/or bloated API.

I'd also rather have an API than panics than an API that silently changes my inputs to be valid (or drops invalid inputs), although I feel less strongly about that.

@danderson
Copy link
Member Author

I'm wary of documented panics, because that still leads to runtime failures. However, my main interest is in getting consistency between all our types, and I don't really like any of the available options. So, if y'all like one of the options, let's do that, and I'll replace my in-flight PR with one that implements whatever we decide.

@josharian
Copy link
Collaborator

However, my main interest is in getting consistency between all our types, and I don't really like any of the available options.

My feelings exactly.

@danderson
Copy link
Member Author

Thinking on it a bit more, I think I actually prefer the "silently fix things up" style of API: if you pass a zoned IP to a place where zones make no sense, it gets stripped out. If you pass a zero IP into something, it does nothing (which makes sense, you handed me nothing, I do nothing). I think our docs can do a good job of saying "look, if you tell us to do something nonsensical, and there's something reasonable we can do instead, we'll do that." Especially because a lot of the edge cases concern zones and invalid objects, which should be unusual in the first place. Let's optimize our design for people who used one of the errorful Parse* helpers to construct valid objects, and who aren't doing strange things with zones.

@josharian
Copy link
Collaborator

if you pass a zoned IP to a place where zones make no sense, it gets stripped out.

The thing that makes me nervous about this is if you are using an IPPrefix to (in your mind) hold an IP and a number of bits. You might reasonably expect that ipp.IP() will give you back what you passed in originally, even if in the context of a prefix it didn't make any sense. The result is a subtle and probably rare/latent bug, rather than a highly visible, easy to diagnose early failure.

As I said, though, I don't feel strongly about this.

Maybe going back to specific, concrete API questions will help.

In the particular case of IPPrefix, I'm most inclined to accept that they're a bit too big, and have an easy to understand, easy to implement, easy to use API.

There's no such easy out for IPSetBuilder, unfortunately. One option might be to change IPSetBuilder.IPSet to return an IPSet and an error. Then we can return whatever IPSet the user has constructed, and if they ever added something invalid when constructing the IPSet, we can also return an error telling them that. Then the current behavior is equivalent to ignoring the error returned from IPSetBuilder.IPSet. And most of the IPSetBuilder API remains lightweight.

@danderson
Copy link
Member Author

Discussed during our design session. We cannot reasonably prevent the construction of invalid values due to zero values, so we're keeping the current behavior of having Valid functions.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants