 So yeah, I'm Sam Whitehead. I'm a sometimes work for the XMPB standards foundation. I'm not sure why that's on there. I think I copy pasted from some old slides, but I'm also currently employed by Cloudflare. So obligatory we're hiring. And a medication will go contributor. And I'm gonna talk a bit about the empty interface. I called the talk the case for empty interface, but I'm not really arguing for it. I'm just kind of trying to to figure out when is a good time to use the empty interface and when is it a problem. So I suspect everyone in this room, I suspect I don't have to give an introduction to empty interface for this crowd or why it can often be a problem. It's one of those feature of go that ends up giving you a lot of interesting problems, but is also very useful. And I will start with some assumptions that I think will, if you don't know what empty interface is, they might explain a little bit about it. And if you do, these are just some assumptions you'll need to know or need to agree with potentially if you're going to agree with this presentation. So the first one is in go interfaces should describe behavior, not data. I think that's one of the go proverbs. And it's kind of part of the basis for this talk. Also the empty interface is easy to abuse and thus is abused. This is just something, if you don't agree with this, you know, we can argue afterwards, but that's sort of the fundamental underlying assumption for this talk. Also, anytime I say empty interface, you should hear optional dynamic typing. And if you don't know what empty interface is, that's the most succinct description I can give you. And if you can describe your behavior with a more specific type, you should. I think this is another one of the go proverbs. And finally, possibly the most controversial, heavy use of reflection leads to difficult to maintain code. And if you're a Ruby developer and disagree with this, then go attempt to modify one of the encoding slash JSON or XML packages and then get back to me afterwards. And those are sort of the things you need to understand going in or maybe not agree with, but it would be helpful. So let's start with the encoding packages. I'll be using an example from encoding XML, but it's exactly the same in JSON, pick your poison. We're going to look at the Marshall API. Now, from the user's perspective, Marshall is an elegant, maybe even beautiful API. It's encoded right in the function signature. I give it some arbitrary data, empty interface, and I get some arbitrary representation out. And that's great because the producer of the value in the empty interface, the user of the library, doesn't really have to care about the data. They just put some data in and get some data out. It's very, very succinct, very nice to use, works very well. There are other methods if you need more control. But when you start digging, and this is all, this empty interface says nothing is one of Rob Pike's famous Go Proverbs. And that's really all you need in this specific API. Nothing. You don't need to know anything about the value of V. But from the package developers perspective, this leads to, from the XML package developers perspective, this leads to a lot of problems. It's obvious what to do with V if V is a type from the standard library. If it's an int, we make up some representation for an int. And we can do a nice simple type switch that decides is this an int, is this a string, is this some other type. If it's something defined in the XML package, like the XML-martial interface, it also has a predefined behavior. The XML package can know if it's an XML-martial, call it's martial XML function. But as soon as we start getting into user-defined types and more complex types, like some arbitrary struct that is not defined in the XML package, it becomes really difficult for the XML package to know what to do with that data. On this struct, the XML package knows nothing about any of the fields. It doesn't know about name. It doesn't know about tags. It doesn't know about the layout of the struct. Now, we could limit the usefulness of the XML package and not support user-defined types, but that's not going to be very useful. People aren't going to use our package. So the way that information has to be gathered is using reflection. And that leads to effectively two separate code bases. Half of the package is reflection-based. Half of it is normal idiomatic go. And it's very hard to reconcile these sort of two distinct code bases. Hard to get data between them. Hard to read and process the reflection-based parts of the package if you're trying to work on it. So it's great from the user's perspective, not so great from the package maintainer's perspective. This use of empty interface. So when the producer of some data, the user of the package in this case, does not care about the type, but the consumer does, the XML package, which is consuming that empty interface, the library becomes difficult to maintain. Reformulated, that gives us our sort of first rule for using empty interface. The producer of the empty interface, the value in the empty interface, must also be the consumer of that value. So let's talk about another package, context. And context is actually going to have problems because of this rule, and we'll have to modify it a bit to make this work. So if you haven't used context before, this is sort of the basic trimmed down version. Most of these examples will be a little trimmed down version of the API that we care about. Context is effectively like an associative array or a map. You have a key and a value, and you can index things by that key and get things out. The implementation doesn't really matter. But in context, the key and the value are empty interfaces, and the consumer is also the producer, like that last rule. Most of the time, when you create a context, you are, for instance, doing it for some HTTP middleware, and your package has a, oops, that's not right, your package has a, say, get session ID from context and insert session ID, or new context with session ID function. So you are both creating and consuming from within that package the empty interface value. And this makes the context package a study in simplicity. If you've never looked at the code, I really recommend it. It's very simple, very elegant package, but it has some other problems. So let's look at a quick, the documentation for context. Package context defines the context type, which carries deadlines, cancellation signals, and other request scoped values across API boundaries and between processes. Request scoped values is the kind of key word here. What does that mean? It's sort of a, we don't really know, this is API as, or documentation as API, because this wasn't something that was encoded in the API we have for context. Nothing in here gives you any indication of what a request scoped value is, or how context should be used. So these request scoped values, I think the original idea was to have things like session ID, request ID, maybe a trace ID if you're using some sort of tracing. Basically anything that's a simple value that ends in ID. Maybe that last part's a little bit of a stretch, but very simple values. Unfortunately, I suspect we've all seen context abuse to do things like this. A lot of times I find new Java developers, for instance, that are coming in to go, or other languages where you're used to a pattern like this, discover goes dependency injection package, and love to abuse context to put in database connections and metrics and various loggers. Don't do this. It's terrible. It leads to a maintainability nightmare because this is effectively an undocumented side channel API. I suspect we've all run into this at some point where you say, well, how am I getting my database connection in my middleware? And someone says, if you're a web developer, and someone says, oh, well, that's just in a context based on a key in some package somewhere. And none of that was in the function signature, and none of that was in the API, and you had to kind of dig through and find it. And then you're trying to type a cert on a thing in a context and figure out if it works, but maybe it doesn't because someone's inserted the wrong middleware in the chain, and it just becomes a nightmare, and it's very hard to use. So this is a sort of more specific example of one of those. This is the common, no, really, don't do this with context thing. Because you see there's a, the kind of basic middleware pattern people use when they're abusing context this way is you get the context, you insert a value with, or create a new context with that value, and then you create a new request with that new context and pass it on through your middleware chain. None of this really is tied to the request specifically, or none of this guarantees that the value you're using is really request scoped. So this kind of leads us to our second rule. The way, the reason this is abusable as a side channel API is because this context leaks between packages, or this empty interface leaks between packages. So empty interface should not cross package boundaries, otherwise it's able to be used as a side channel API and kind of slip, smuggle data through into packages where it's not supposed to be. Finally, we'll take a look at a third package, and I couldn't find a good example of this in the standard library, and I'm sure one exists. And if I, if there's a much better example I hope someone will tell me afterwards. So I'm going to use an example from one of my packages. Sassel is a, an authentication framework that's widely used. You probably use it every time you log in to check your email with SMTP, or you probably use WhatsApp or Facebook chat or something. I don't actually know what they use, but it's just a widely used potentially multi round trip authentication framework. So I have a little go library that uses it. And the API looks something like this. We have a mechanism, which are things like plain username and password authentication auth to various kind of authentication systems. A negotiator, which is a state machine that actually handles the authentication flow, make sure that replay attacks aren't possible, make sure that that general security constraints are enforced. And that has a function that the user of the package calls to advance the state machine and accepts challenges and returns responses and you can serialize that in your whatever your chat protocol or your email or whatever the protocol is and send them over the wire. So as you can see the mechanism API takes and returns an empty interface. And this I think is actually a pretty good use of interface because this mechanism might be defined by a user in their own package. But the way it works is every single time the negotiator step method is called we're moving we've gotten a new authentication request or something over the wire, and we are advancing the state machine and trying to do some sort of authentication. It calls this mechanism as part of its work calls this mechanisms next function that's the user defined whatever OAuth2 specific what we need to do thing that returns some data since the sassel might be multi step. In a future step, you might need a value from an earlier step. So for instance, in scram, which is just is a password based authentication where you never send the password, you do a proof of possession. One of the ways the server make sure it's still talking to the same person is it returns a signed version of the very first message that was sent. Which is also something I think TLS does and so you have this data you might need in another step. Unfortunately, for reasons that don't really matter here, mechanisms have to remain stateless or don't have to but it's because this is a thing users are writing and is authentication based. It's important to keep them stateless because keeping things stateless makes them simpler and auth code is notoriously difficult to write and difficult to get correct to begin with and is security sensitive. So we want to make things as simple as possible. But there is this state that needs to be stored somewhere. So since the negotiator, the state machine is already stateful for obvious reasons. It's called a state machine. We can return that state that needs to be stored. The negotiator will store it for us and then when it calls next again in future steps, it will pass that back in as the data parameter. That all sounds very complicated and it kind of is. I wish I tried to draw a flow diagram but for some reason I couldn't get box drawing characters to show up or work at all. I have no idea why. And I tried to do a JPEG and it was awful and I kind of gave up after a while. But the idea is data is returned from cache and then the next step passed back in as the data parameter. And this keeps things very simple. It means the producer is always the consumer. This next function always puts a value into cache and is always getting it back in data. And that means that in practice the next functions are generally always structured like this when people write these mechanisms. You end up with something like a step that checks the states. This API is not what it looks like in the actual package. It's sort of simplified. So it might check are we in step one, step two, step three with a switch or an F or something. Put some data in this case a randomly selected integer into return it for use in a future step and then because it returned it, it can always assert on the type of that data and it knows that it will never be mistaken. It's very easy to test and it's very simple and easy to read. I think this is a slightly better use of empty interface and it follows these two rules we've mentioned before. And this is the point in the talk, I think, where I have a bit of a confession to make. I've been deliberately wasting your time. Both of these rules actually kind of simplify down into a much simpler rule that encompasses both of them. And it took me a while to realize this, which is why I bothered with the rest of the talk because hopefully it kind of gave you a few examples first that will make this make more sense. My real rule for using empty interface is you must always be able to type assert on the value in the empty interface. And if you can't do that it's going to lead to problems. So empty interface gives you, it's not that it's useless, it's easy to say well if I can type assert why don't I just return an int. But in a lot of cases you do need arbitrary data. In some languages you would use generics for this in others there are other mechanisms. In go that's sort of a mix of empty interface and closures. But if you are using the empty interface and you have some arbitrary data that you need to return it needs to be known arbitrary data. You must always be able to type assert on that data and that will make sure that you don't run into maintainability problems later. And I think that's sort of the core rule when using empty interface that needs to be followed. There's a lot of other examples if you go dig through the standard library but these are my kind of three rules for using empty interface. I'm going to end really early. That's really it. So if anybody has any questions I'd be happy to take them in the back. So defining a method, I'll repeat the question but that's a validator method is what you're asking about or like a validate interface value. I'm not sure that I followed the question I'm sorry. So I think the question, stop me if I'm wrong, I think the question was is it a good idea to define methods like is integer value or something for your empty interfaces? Yeah, I don't think so. I don't think it's a bad idea but I don't think it's necessary because I don't have an example on here. But because you can you should be able to type assert in my opinion. If you can't though if for some reason you really do just need to check if a value is a certain thing that's what type switching is for or types or type assertions with the optional okay parameter. So I don't see the use in having a separate method but I may have misunderstood the question. Anybody else? Oh right. So the question is at what point could you type assert in the Marshall XML example and you really can't is the problem. That's sort of why this was one of my early not a bad example. It's a great example from the user's perspective. It's a very nice API but it's very bad from the from the developer's perspective and the reason is like I said it's hard to type assert. You end up having to do this are lots of reflection to get because you can't type assert because you don't know what the type is. You have to do a lot of reflection so it you can't is I think the answer. I don't know a way not in this I would love to know a better way to do the encoding package and go but I haven't thought through that. Anybody else? I guess that's it. I'm sorry I'm very early. Thank you.