Golang UK Conference 2015 - Andrew Gerrand - Stupid Gopher Tricks

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey isn't this a fancy venue it's quite nice I feel like I'm at some White House press dinner or something so yeah I'm Andrew and I work on go at Google in Sydney and this talk is called stupid go for tricks it's really a bad title but I sort of chose it and now I'm stuck with it it's to talk about things that you might not know about go depending on how experienced you are with the language and I hope that you know some of it you can find useful and actually add it to your sort of tool set of things you might want to do but some of it is definitely things that you should never ever do ever and actually Rob wanted me to make sure I explicitly say that and I'll point out where that's where that's true so I'm going to talk about the language the libraries and tools I'll start with the language so also this is very very dense code down so I hope that you've had coffee and then you can see the screens so here's the type declaration it's a struct type and the part after type foo is a type literal and we we use type of literals all the time if in so there's also intz and slices and channels and so on but it's the part after at the end of the type declaration and I just wanted to draw that distinction because we commonly use you know type declarations and function signatures and variable declarations and they're usually types like int but it's also possible to use the type literal for structs in all of those contexts so here I have a variable T that is a structure type and these are usually called anonymous strokes so one common usage of anonymous structs is when you have like a one-off use for a struct and I common I often use this when I'm populating a template with some data so here we have a variable data that is a struct type and this is the contents of the struct and then I can pass it into my template execute function and if I run it I get the template Oh God it's also possible to use a map of string to empty interface to do this same thing but unfortunately in that case you sacrifice performance it has to actually allocate a map which and populate it which is a bit slower than populating a struct and also you lose the type safety of each of the values so struct as far superior in this case it's also nice to use anonymous structs when working with JSON objects often you want to encode or decode a JSON object and you only need the struct representation of that object in one place and particularly if you're like reading some big JSON blob and you only want to pull one field out of it you might just want a single struct so here's two instances one of them encoding objects or I have this this structure type here in the in the Marshall as the argument and so it's a struct literal with with some values and so it encodes like that and then on the flipside I can declare a local variable data to decode into and so I pass a pointer to that struct into unmarshal to get the same thing our also struct literals can be nested so you can have an anonymous struct that has an anonymous struct as one of its fields and so if you have some JSON data that has objects inside objects you can achieve the same thing and so we can see I was pointed out in the review for these slides that George Costanza never actually was an architect but he was you know the best architect one could aspire to be I think go also lets you omit the type name and when you have repeated literals like slices and maps so in this case I have this struct foo again but where I have a slice of foo I can actually list out each of the structs without putting through here at the beginning and that works and when you combine this with anonymous structs it actually makes it much easier so if I have this slice S which is the slice of this structure type if I wanted if I had to populate the type name every time I'd have to do this big ugly anonymous struct in front of each but fortunately I can just omit that entirely so T is is definitely what you would do there and that leads to much nicer instances of slices of anonymous drugs so if you have tests that have test cases it's quite nice to declare them locally inside the test function instead of having to declare them outside with some you know you were you wouldn't to have like type index tests struct outside you can just put it all in the one place so here I have a slice of test cases and I can either write over them if you want to go one step further you can actually put that struct literal inside the range statement itself so you don't even need the local variable it's been pointed out to me that this is a kind of harm's readability of it you kind of by the time you've started reading the middle of this struct literal you've probably forgotten that you're actually in the middle of a for loop so probably don't do that and so embedded fields also fit into this picture so if you have a field for those of you who don't know if you have a field in a struct that has no name it's an embedded field and embedded fields have the property that any of their methods and also any of their fields if it's a structure that you've embedded are now accessible as if they're part of the embedding stripe to the outer struct so here I have a type a which has a single field s a string and it has a string method on it and I embed that struct a in struct B and then if I have an instance of B I can assign to the B's field S which is actually a SS and if I call print line of B it invokes the string method from a and so that's that's a really convenient property and useful in many ways but when you combine this with anonymous structs you get an interesting kind of utility so for instance if you have global variables that need to be mutated and accessed from multiple garbage in simultaneously one way of doing that safely is protecting with the mutex and a common way of doing that is you have your variable and then you have some mutex which has the same name like with mute act on the end and so whenever you want to access view count you take the view count mutex and update the view count and release it but if you use if you embed that mutex in an anonymous struct you get one single name in your global namespace view count and then it all the all the it's clear from from a documentation standpoint that the mutex protects those fields and when you use it you're only accessing this one name view count which is much much cleaner little thing we can also use structs embedding instructs to implement interfaces I'm in an ad-hoc kind of way so here's an example from the camlistore project where there was a function signature which returned a read see closer so the union of i/o reader io seeker and i/o closer but the programmer in this case actually only had a string so they needed a string to implement read seek enclose and so it was Brad Fitzpatrick who wrote this he created an anonymous struct with the fields with the embedded fields read seeker and closer and then he used a IO section reader to wrap a string reader so that so the string reader gives you a reader that yields the string the section reader gives you a read seeker that reads the string and then he can post that with an IOU till not closer which is a standard library utility that gives you a you give it a reader and it gives you a read closer that has a close method that does nothing and so he's given it a nil reader so that he has a close method that does nothing because closing a string doesn't do anything and so this is particularly esoteric and strange but I was told earlier today by someone that they could have actually used this somewhere so I hope you can too it's actually not not that gross and structs are not the only thing that we sort of can consider anonymous interfaces themselves can be anonymous as well so you know if you have an interface declaration type through interface the interface part is actually a type literal as well and we see these all the time we see the the empty interface used pretty often which is a type of literal itself but it doesn't need to be an empty interface for instance I can create this variable F that is an interface type which has a string method and then assigns something that implements string out to it and then invoke the string method on it so this is a contrived example but one way in which this is really useful is you can do an inline type assertion so if you don't want to necessarily declare an interface for this one shot type assertion you can just do it in line like that and this is something that I've seen pretty frequently I've also seen people do this in type switches as well so that's another cute little trick so method values method values are what you get when you evaluate a method as an expression and it gives you a function value that you can use you can invoke and pass around like any other function value and so if you take a type like Y its buffer and then evaluate a method on that type so here I have a bytes buffer type name and I take the the right string method it gives me a function value of this function signature so I get right string is a function that takes a method that takes a string and returns an int in an arrow but the method expression function has a bias buffer as its first argument because it's that's the receiver for this method right so when it turns into a function as opposed to a method I get the receiver as the first argument and so it means if I want to call that function f that I've just created I need to pass in abide stop buffer as the first argument and you know in this instance I wrote Y you know buffer I'd string because this is equivalent to just writing off dot right string and the string and it's obviously not not much cleaner not much more useful but if you evaluate a method on a value so so in this case I have a buffer why it's buffer value and I take the right string method as a function and store it in F this time I get the same function signature but without the receiver as the first argument so I just have a string and that actually ends up being kind of clean you could see it as a way of reducing the repetition of invoking that method or passing it around in fact because this particular buffer receiver is now enclosed in that closure that you can pass around and we'll see some examples of how that works so if I run that you get a little message so one way in which this is practically useful is using when using sinked at once so sink dot once is a primitive that lets you perform a single task safely in a concurrent context so if there are multiple go routines that call the once values do method giving it a function literal only one of those go routines will execute the function and the rest will all block until that function has executed and then perceived so it's great for doing like lazy initialization if you have something that should only be initialized once and it has to be initialized for every user of that thing it's a great way to do it so here's an example where I have a type called lazy Prime's which inside and has a slice of prime numbers but I don't actually want to compute the prime numbers and populate that slice until the first time somebody wants to use it so there's this primes method and that returns the slice of primes and inside it calls the the once values doom method passing in Peter in it as the as the do function so is this as this function and in this case the method value Peter in it it it encloses this receiver into it and so this init method is called on this particular instance of lazy roams and you get to do the initialization so that's one nice little clean clean example I mean it's it's functionally equivalent to writing a function that calls Peter in it like that and placing that in the same place if that makes sense another cute example is you can use method values to implement a bunch of HTTP handlers on the same time so usually an HTTP handler method has to be called serve HTTP but if you use each of these methods as a handler function as opposed to a handler implementation you can so here I have index edit and delete three HTTP handlers on this server type I can have a register method that takes the serve marks and registers index edit and delete each as HTTP handler functions and so in this case the server is is enclosed in each of these function values and it's just like invoking the method and this is another example from the standard library internals the exact package has a bunch of methods to set up standard inputs and output and standard error on processes that are executed and the setup is different in each case but the clean up is the same in each of them and so when the command is being set up for execution we have this this range loop over a slice of function values and each of those function values is actually a method expression on the comb type so I am doing this body for each of the standard in standard and standard error methods and so I call each of those methods which we called set of fifty and then I do the the clean up each in exactly the same way and I can also then you know append each of those file descriptors to be cleaned up in the same way that's a real example I want to talk about comparable types so who here knows what the notion of comparability is in go alright so good hopefully learning something this so the ghost spec actually defines comparable as a concept and basically any go type that is comfortable with equals or not equals is comparable and the types that do satisfy this boolean Zins floats complex numbers strings pointers and interestingly channels structs and interfaces are also comparable and so here are some examples you know if you have two integers a and B you can compare them if you have to interface values whose underlying type is is comparable then you can compare those so I'm assigning a and B to I and J which are empty interface values and when this comparison happens it first compares the underlying type of the interface value and then it compared and if they correspond then it compares the the underlying concrete values and also you can compare struct values so in this case actually have a struct with an interface value inside it and the interesting thing about comfortable structs is that they're only comparable if each of their fields is also comparable so that property sort of is a trance it's transitive and so here's an example of a structure that is not comparable so Q and I are both structs but there there only feel there's a slice and slices are not comparable and so we get a compile time error that struck the starts containing a string slice cannot be compared so comparability manifests itself in a practical sense when we work with maps only comparable types can be used as map keys and any comfortable type can be used as a map key so here's a common sort of thing you might see using hints as a map key that works obviously a pointer type will also work as a map key and also concrete struck types are usable as map keys and also you know you can also use a map of empty interface which you can take any comparable type as the key and I just want to stress that you should never do this don't ever if you ever see yourself doing map of empty interface to something you're wrong just don't do it there's no really good reason to do it you sacrifice type safety you open the door for things to explode a runtime don't do don't do this good ok but here's a real example of using a struct as a map key so in the continuous build infrastructure for go that builds go on a variety of platforms like Linux and Darwin and so on we have this notion of a builder rev it's a struct with two fields the Builder which might be like Linux amd64 or Darwin 386 or so on and the revision the the the gate revision that's being built and we track those those builds in various ways and one of the ways we track them is in a map so we have this like map caught in flight and it's key if we were doing this before go one when we couldn't use structures as map keys we would decompose that struct into a string into a string form and that works that works fine but it means that every every time we read or write from that map starting with the builder rev we need to allocate a new string and it's allocating a string that is allocated already it's in the map and it's probably all over the code and so in you know high-throughput environment it's obviously a lot of pressure on the garbage collector that you don't need but if we use a struct key if we put the builder rev as the struct key we can just use that build a rev value itself as the as the key in the map and so you avoid the allocation and also the code is a lot cleaner to read interfaces map keys are a little a little less often used but here's a real example that I found on the internet which is from Dockers code base they have a package called broadcast writer that for every write you make it implements writer and then it lets you add writers to it so that when you write to the broadcast writer it writes to all of the other writers I'm not quite sure how they're using it but it seems pretty interesting but it has this map of write closes - empty struct so they don't care about the value they just want to track the write closes that they have and you know when you add a writer it adds it to the map and when you do a riot it iterates over that map to do the writing and if I write fails it deletes that writer from the map so that's it that's a kind of cute little thing and here's an example that's quite silly and very contrived and you should never do anything like this ever I have this type cons which is like a lisp console it has a value and then like a tail car and cutter as they're called and it implements string which kind of Deacon flattens it out but I have this main function which has a map of cons to string and I am constructing a console and then iterating over a slice and then adding to that console to create a a series of nested struct FAL use which I then use with a map key and store a string and then I am printing the map key and the value and so I get this string and that was kind of what I was thinking while I was writing this example but the interesting thing here is if I you know if I populate the cata field of my initial cons with something that's not comparable like a slice of strings and I run that I actually get a runtime explosion so I get a hash of a none hashable type slice of string and so this is another reason why you shouldn't use interface values and particularly empty interface values as struct keys because you open the door for runtime explosions like this which are bad I'm moving on to libraries here he's used the sinc atomic package that's a little more than I expected maybe a little more than is necessary but who am I to judge right earlier we saw the simple view counter where we we were using a mutex to protect an INT and incrementing that int while holding the mutex another way of doing that is with the atomic package where you can atomically read and write and update values using the newest processor architectures native primitives for doing these kinds of atomic operations so if all you're doing is operating on a single word of memory often the atomic package will yield a lot better performance and in fact a lot of the types in the sync package are constructed using these atomic primitives and so you could use them in theory to construct your own concurrent primitives but I think it's probably unlikely that you would want to do that but so for a series of types like in 64 and 32 and so on there is a series of functions like add compare and swap Lorde's door and swap and so in the case of the view count i would have some global view count variable that is in 64 and very important i would document that this should be updated atomically it would be nice if there was a tool to verify that maybe France s we'll talk about that later in the day maybe no one and then if you want to increment it you can use add in 64 and pass a pointer to that value and so we'll use the processes atomic instructions to make that update and return the the new count there's also another tool in the atomic package called atomic code value which lets you actually store any interface value and load and store that value in a in a concurrent safe way so for instance you could use this for sharing configuration if you have some global configuration for your service and you want each of those handlers to always see the the latest version of that service you would have a global config atomic value and then you can store it using the config store method passing in your new configuration and then each go routine that that's running so like at the top of each handler if it's an HTTP server for instance you would call the load method and lo it actually returns an empty interface value so you have to convert that to assert that to its concrete type and then you can read the configuration if you try and store different types in the same value it will cause a panic and we'll see why right now so when I first gave a version of this talk at the start of the week someone asked how does a Tomic dot value work and I was like I don't actually know and Brad said read the source and I read the source and it was actually really interesting so I thought I would show it here as well atomic dot value itself is a an interface value on the inside so the a single atomic dot value has the same memory footprint as just any interface value which is two words of memory and the way that's represented by the runtime is actually two pointers and this is a this is a struct that's in the atomic package it's not actually used by the runtime but it's used by the atomic package as a representation for the same data structure in the runtime so the the data structure contains two fields both of them pointa sized one of them represents the type of the interface value so the underlying concrete type and the other represents the data and it's usually a pointer to the data of the interface value and so for atomic dot value to operate on an interface atomically it actually uses pointer atomic pointer operations to load and store each of these fields but it has to do it in a sort of careful way so if we look at at the store method on value first the first thing it does is check that you're not trying to store nil because that's invalid and then the next thing it does is it takes it takes V which is the value itself and it takes X which is the the value that we're storing in the atomic value and then it uses unsafe pointer to cast them into pointers of interface words so instead of having an empty interface value we now have a pointer to to this struct and we can modify these these fields like any other struct but we're actually operating on the internals of the interface value and so once we have V P and X P we want to store X P into V P we need to spin so most atomic operations will spin trying to do something until certain conditions are met so we have a for loop that first loads the type the type field of our atomic value and if the type is nil it means that we haven't stored anything in that atomic value before it's a new atomic value and so the first thing we try and do is store a sentinel value so we use compare and swap point of to try and store an unsafe pointer of not you in pointer zero so that not you in point of 0 is you in pointer flipped with all bits to one so it's like max you in pointer which is you know on a 64-bit machine it's whatever 2 to the 64 is and so it's incredibly unlikely that a real type will ever have that value I think it's pretty much impossible you'll have that many types in a go program but once if we're able to if we're able to stick this sentinel value in the type field then we can proceed and start storing the real value that's what happens here so if we stick the central value in we can then put the update the data field and then stick the real type in over the top but if we fail to put the sentinel value in it means that there is another concurrent store happening and we need to loop back again and try again so if we don't find that type is nil which will happen if there's already a value in the atomic value we instead need to check whether the type is the sentinel value in which case there is another concurrent store happening and we should try again so we do a continue again but if there's not the central value then there must be a real type value in there and so we will check whether the type is the type we're about to store and if it's not we explode so this is why you have to use a type of the same the same type and whenever you store and if it's if it is at the same time then we can go ahead and simply update the data field so I hope that wasn't totally lost in you guys but on the on the load on the loading side it's much simpler when you're reading a value out we do the same thing we deconstruct the value into an interface word struct and then we load the type if the type is nil or it's the sentinel value then then we're currently storing a value and so we'll return nil otherwise we can just load the data field and create a new interface word struct and stuff the the type and data into that and then return it all right so with that said usually you don't want or need any of this stuff in the atomic package I know a bunch of you said that you'd use it the chances are you might have been able to do it better without it I suggest if you want to do anything any concurrent synchronization you should try channels or the sync package rather than the sync atomic package first if you find those tools lacking then you should start exploring the atomic package but even if you are using the atomic package don't try and write something like atomic value where you're reaching into the runtime the representation of interfaces could change at any time it's an implementation detail so if you rely upon those things you just ask them for trouble also in showing that example I didn't actually show the entirety of the implementation while we were doing the spinning update the there were some calls to a function in the runtime itself which would pin that particular go routine to a particular process of preventing it from being preempted by the scheduler which is critical when you're doing these kinds of concurrent operations you don't want another process to be scheduled in the middle of your concurrent update and only packages in the standard library can actually hook into the runtime like that so you can't write this code it in the in the same safe way that you can here but it's good to know how it works and knowledge is really valuable alright so moving on to tools I want to talk a little bit about testing a couple of techniques that maybe you you might not be familiar with sometimes when you're testing you want to test the behavior of a process rather than just a function or maybe you have a function that affects the estate of a process and so it's not adequate to do that within the context of a regular test and so this this function crasher it makes the process exit with the status of one prints a message first obviously if we want to test that with the testing framework it would just cause the the test binary itself to exit and we would never see any test results and so one way to get around that is to have the test itself spawn a the test binary itself as a sub process and so in test crashing here if in this this test method this test function we first see whether an environment variable B crasher is set if it is we run the crash of function number two so this this is the pathway that will be followed if we're the sub-process if we're not the sub-process then we want to create the sub process so we use exec command to exec host at odds of zero so that's the test binary name itself and then we pass the flag run test pressure so that we're going to execute only this particular test case and then we add to the existing environment the be crash sure environment variable and execute it and then to see whether the test was successful we checked the exit error and see whether it was a success or not if it wasn't then we will print out what the actual exit status was so that's that's a very stripped-down example but sub process tests can be useful in many ways analogous to sub sub process tests sub process benchmarks so go has CPU memory and memory profilers who's used those before not enough but enough some okay but the way those profilers work is they report information of an entire process so if you have a benchmark that has two sides to it so you want to test a server you want to do a profiling on a server you need some kind of client to access that server and so you can use the same technique to spawn a sub process that is a client hitting that server and then you do the profiling only on the server part of that whole operation so here's a real example from the HTTP package where we spawn a child process to make requests to a server that's under test and so we start off by turning allocation reporting on and then we do the same thing as we did on the previous slide we check an environment variable to see whether this process is the child process and so if this if this environment variable is set then we are and in child process mode we hit the the provided URL n times making HTTP requests and there's an error handling and so on in there which I've emitted and then we exit avoiding the rest of the the benchmark function which is the server side of the code so it creates a HTTP test server that says hello world and then it uses the exec package to execute the test binary itself running the benchmark server providing these environment variables and so the end result is you can run go test running the benchmark with the CPU profile or mem profile flags and generate nice profiles that only include profiling data for the server side of this proportion if we have the client in the same process we would have confusion over which parts of the profile reflected the client side and which parts reflected the server side all right and my my final trick is go away who here knows of go list who here is a used go list so go list is a sub command of the go tool and it prints package names for all the import paths that you that you supply so if you say go list by its it will just print by its if you say go list with a wildcard it will print all the packages that match that wildcard and there are some keywords like go list stood which shows the standard library for go list all which will show you all packages and go list has some interesting flags and has a JSON flag which will just dump out everything that the go tool knows about that package so there's tons of information in there useful things are the directory in which that package lives documentation what the build target is whether the build is stale so whether go install would actually update the build target or not and also the files that comprise the package so there's actually a struct in the documentation which specifies all of the fields available in that JSON blog and so this is one third of it this is the next third all the different kinds of source files that might make up a package and also any CGI directives dependency information the packages direct imports as well as all of its transitive imports so that's imports and depths it also includes error information if there was an error in parsing the package the package will be listed as incomplete and you can inspect what the error was in loading that package I mean you can also see what the tests are in test dependencies and so on and so with all of that information you know you could dump it out with the JSON flag but there's other ways of using that information and one of them is with the dash F flag which specifies the formatted output of the go list command and the dash F flag actually uses goes test text template package as its language and this the data that's supplied to that package is the that package struct itself so there's one template invocation for each line of output from go list so for instance if I say go this dash F with just the doc field as my template for everything under the or through package I get the first sentence of the documentation for each of those packages printed to standard our standard output or I can let use go files as the template directive and see all of the go files that comprise that package and you'll see that the template package prints that slice with the little square brackets on either side if I wanted to make that a bit cleaner I could say girlís of join go files by a space so join is one of the available template functions I'm documented in the template package and then I can see those file names without the the brackets and then once you introduce template logic you can start testing for conditions and you can start filtering on the packages that are output and so if I wanted to find the standard library packages that don't have documentation strings I could have a template directive that is if not dark print the import path and so for the standard library that just shows to internal packages even though they're internal they should probably have Doc's but it's probably worth running something similar over your own code base and see where your documentation is lacking it's also possible to find packages that depend on a given package so in this template directive I'm iterating over the dependencies and if at a particular dependency equals the oauth2 package then I'll print the import path and so this shows me all of the and I'm doing that for all packages in my workspace so I can see all of the packages that I have that depend on or through and so I can see that some of the continuous build infrastructure uses the overthrew package and that's really useful if I'm making some updates to or through and I want to see what packages might be affected by that it's also nice to be able to list which packages are broken in some way so if I pass the - a flag go list will not print errors to standard output and and continue working even if it encounters errors so that's critical for this example and my template function is with error just print the error string and so for each package that has some error State I can see what the the error condition is and among other things in my workspace I found that I had an old version of our through under the old import path that the go tool doesn't like because there's an import comment that says it should be called drilling - Rob / + x / / - and so that's another one that you might want to run over your own code but once you introduce shell scripting in to go list things start getting even more interesting so you go lists output by default lists import paths so and go lists command line takes input paths so you can very easily use the would have gar list as the input to another gar list command so for instance if I wanted to run all the packages sorry all the tests for the packages under my working directory I would say go test dot slash dot dot but if I want to run all the tests of my packages but not my vendored packages I can say go list of those packages and then do an inverse grep for slash vendor and then use that as the arguments to go test or if I wanted to just test the dependencies of a specific package to make sure that everything if I'm encountering some weird bug and I want to make sure that all my dependencies are all ok I can say go test of a go list of all the dependencies for this given package and you know if I wanted to do the same thing but I don't want to waste my time running standard library tests because I'm running the stable version of go and I trust that the tests pass I can do the same thing I go list the dependencies for all two but this time I do another go list to filter that dependency list where I'm saying if this package is not in the guru to print the import path so will only give me input paths that are outside the go route and then I pass all of those in to go tests but I got to go a step further of course if you wanted to print the line counts for a given package or a set of given packages you could do this you have a batch for loop which iterates over each package in the oauth2 repository in the variable package and then for each of those I do a go list with a format of each go file in that package and I add the package directory to the front of that go file so this this command will give me all of the growth the absolute paths of all of the grow files in their package I pass those into word count - L and then I use tail to trim off the very last line which will be the total or a single file if it's just a single file in the package and then use trusty awk to print only the count and the package name and then finally once that's done I can sort them all numerically and see that the google package in the earth true repository is the largest one absolutely followed by ola through and you know something like this I might consider actually adding as a shell alias because it seems it seems like it might be useful and so for my final trick I thought I'd do something funny which is writing a single command line that generates an SVG of the dependency graph for a particular package so this uses graph is graph fields has a very simple declarative language for specifying graph information and then it turns it into pretty things like this and so if I in this instance I'm doing a girl list of sorry I'm doing a girl list to get the dependencies of the time package and supplying the time package itself as the arguments to another go list that iterates over each of the imports of each of the packages to output a set of lines that say this package imports this this other package and actually if I want to show what that looks like I can run that on another command line yay I was 10 updates if I run that you'll see that's the output and that's the input that I would pass into the dot command and so you know if you can see that the girl list is actually a really powerful tool and maybe it's something that you might want to start using so there's much much more I have more sort of crazy ideas and there are more interesting corners of gos libraries and tools and even the language itself and so I challenge you to do something similar maybe you can find even more interesting and strange things and to do with go so the interesting thing is about all of this is all of these features are all documented so it's not like this sort of some corner hidden away if you actually read the docs from beginning to end you would find a lot more of these kind of so I encourage you to do that and have fun thanks I'm also happy to take any questions with a microphone coming around Andrew so on the broadcast writers slide yes you could show it up again I wanted to amplify one of the caveats he made about using a map but keyed by empty interface that example actually shows a map keyed by a non empty interface I think it was a writer this one yeah right closer so there's a problem here which is that it's accepting right closes from outside the package okay so it's accepting writers from clients and then it's put him in the map and there's a consequence of that is that if a writer is not comparable it's gonna panic at runtime and yeah that's nowhere documented and generally people don't document that kind of thing so it's just another reason why you shouldn't yes this is Matt Keyes yeah did everyone catch that because if if this writer that I'm provided that the user is providing the I have no control over if it's concrete type is say a slice or a function then this ball exploit a runtime and I don't recall anything in the documentation of this broadcast right a package that said that and the other caveat that I would say about this particular implement implementation and sorry to any doctor folks who might be here or watching is iterating over the writers in a non-deterministic order and also seems kind of surprising I would expect to all the subsequent rites to happen in the same order as the writers were added do you know why slices are not comparable yeah so our slices are not comparable because there's no obviously correct way to compare them so there are different options slices themselves a form of pointer they point to a backing array and so you could compare the slice header so you could say if this slice points to the same region of memory and it has the same length in capacity then is equal that's one way of doing it but that's not actually a particularly useful way of doing it because often when we want to compare slices we actually want to compare their contents and so I think that would lead to surprising behavior where you would have two slices that point to different arrays that have the same values but the slices are not equal and so that's confusing the another approach would be to compare the the underlying arrays and so actually iterate over the values pointed to by the slices and then check whether they are equal as well one reason that's not desirable is it's slow and it can be unexpectedly so and also I might be wrong but I think we don't have any other such similar comparisons that have this kind of unbounded complexity right it's sort of the the time it takes to do that comparison is linear to the size of there the underlying arrays sorry oh oh yeah wait I don't it's cyclic oh yeah if you have a slice that points to itself what does that mean what if you have a slice of slices or what if you have a slice of structs with slice fields you know it kind of becomes really confusing so just like we don't follow pointers when we do pointing pointer comparisons we just compare the point of values themselves and we can't really follow a slice values in the same way and that was a deliberate we definitely talked about doing that and there are arguments for actually doing it but in the end we heard on the side of predictability and simplicity what about letting the programmer define the comparable on it sound struct like at the moment there is no support for that but has been any discussion yeah so the question was why not let the programmer define equality basically or or another proposal has mean allow the programmer to implement like some hashable method so that you could put arbitrary types into a hash map the latter is kind of not that necessary anymore since we have struct keys in maps the form of making arbitrary types comparable it would be somewhat unprecedented in the language so once you sort of head down that road you start talking about making arbitrary types range of all you know like you could have your own collection types that work with the range keyword and it's definitely it's definitely something that's been discussed both during the languages original design period and also subsequently at length but the general feeling is that it sort of steps up a level of a level of complexity and magic notes that that isn't there right now like I know that if I'm comparing two values and go I know what that means at a programmatic level I know that it's not invoking some arbitrary piece of code that's doing the Equality comparison and I think there's a really nice readability benefit to being able to look at a piece of code and see any you know I can look at this expression and I know that it's basically just a comparison in assembly and that's what's happening I don't have to follow a potential equals method somewhere and so keeping sort of keeping that level of magic out of the language was a deliberate design decision generally through experience working with large code bases where such abstractions can lead to confusion any other questions nope alright thanks very much
Info
Channel: GopherCon UK
Views: 18,058
Rating: 4.9024391 out of 5
Keywords: Go (Programming Language), Computer Science (Field Of Study), Conference
Id: UECh7X07m6E
Channel Id: undefined
Length: 53min 10sec (3190 seconds)
Published: Thu Sep 10 2015
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.