1,247 karma · joined December 18, 2009
The math/rand/v2 package has a number of functions which return random numbers of a certain type:
i := rand.Int32() // a random signed 32-bit integer (type int32)
j := rand.Uint64() // a random unsigned 64-bit integer (type uint64)
It has functions which return a number within a range: in := rand.Int32N(10) // a random int32 in the range [0,10)
jn := rand.Uint64N(100) // a random uint64 in the range [0,100)
It also has a generic function, rand.N, where the return type is set by a type parameter. The definition of rand.Int32N (for comparison) and rand.N are: func Int32N(n int32) int32
func N[Int intType] (n Int) Int
Adding some spaces to make the common elements align (apologies if my formatting gets mangled), that's: func Int32N (n int32) int32
func N [Int intType] (n Int ) Int
As you can see, the generic function N has the same signature as the non-generic Int32N, except the type it operates on is set by a type parameter (named "Int"). The type parameter has a constraint, intType, which is a private type defined in the math/rand package. (There's nothing magic about this constraint, it's just a list of all the integer types in the language, and you can write it yourself if you want to. It's a separate type to keep the function signature of N from becoming too large, and it's internal to math/rand because it doesn't need to be part of the public package API.)The nice thing about rand.N is that it lets you write something like this:
// d is a random time.Duration in the range [0, 10 minutes)
d := rand.N(10 * time.Minute)
Without generics, you'd instead write this as the following, which is a lot more noise: d := time.Duration(rand.Int64N(int64(10 * time.Minute)))
The generic rand.N has a more confusing type signature and a lot more language complexity behind it, but the code using it is simpler and easier to read. We think that's a good tradeoff, but of course not everyone will agree.All the functions I've mentioned so far use a default random number source. Each of them also exists as a method of the rand.Rand type, which generates numbers from a user-provided randomness source. For example:
rng := rand.New(rand.NewChaCha8(seed))
a := rng.Uint64()
b := rng.Uint64N(100)
There is one exception, though: Until Go 1.27, there was no Rand.N method, because we did not support generic methods. (A generic type could have methods, but those methods could not be further type parameterized.)In Go 1.27, there is now a Rand.N method:
// Using a ChaCha8-based source with a defined seed,
// generate a duration in the range [0, 10 minutes).
rng := rand.New(rand.NewChaCha8(seed))
d := rng.Duration(10 * time.Minute)
This method's signature is: func (r *Rand) N[Int intType](n Int) Int
Comparing function vs. method and generic vs. concrete: func Int32N (n int32) int32 // function
func N [Int intType] (n Int ) Int // generic function
func (r *Rand) Int32N (n int32) int32 // method
func (r *Rand) N [Int intType] (n Int) Int // generic method
In this case, generic methods permit us to fix a small wart in the package API. This example isn't the motivating reason for adding generic methods, but I think it serves as an example of how adding them makes the language a bit simpler and more consistent. In Go, methods are just a type of function. Previously, you could write a type-parameterized function, but you couldn't write a type-parameterized method. That's an inconsistency that you need to remember. Now you can write type-parameterized functions or methods, using a consistent syntax for either.Type-parameterized methods don't participate in interface satisfaction, so this change isn't without its own subtleties. Discussing the tradeoffs there would double the length of this post, and weighing them is why it took so long for us to decide to add generic methods.
Another possibility is that people will use generic methods to write unreadably complex code. My personal opinion is that nothing will stop people from writing unreadably complex code if they want to; the fix to complexity is to not do that.
Given that net/url has supported RFC 6874 since before RFC 9844 came along, our choices are:
* Keep supporting the RFC 6874 syntax.
* Drop support for it, require strict RFC 3986, have no support for zone IDs in URLs at all. Breaks existing users, utterly infeasible.
* Stop supporting RFC 6875 and start supporting an unescaped % as the zone ID separator, which conforms to no standard I know of. Also breaks existing users, infeasible.
* Some sort of hybrid where we try to support both %25 and % as a separator? Ugh.
Of these, keeping the existing support as-is until or unless a new standard comes along seems like the best option.
RFC 6874: Representing IPv6 Zone Identifiers in Address Literals and Uniform Resource Identifiers (https://www.rfc-editor.org/rfc/rfc6874.html)
Which says that, yes, you need to %-encode the %, so a URL containing a host of fe80::4%eth0 becomes http://[fe80::4%25eth0]/. Yes, that's ugly. Sorry.
> TL;DR: computers were a mistake.
I agree entirely.
(For what it's worth, I am a maintainer of Go's net/url package, and I believe net/url correctly handles zone ids in URLs. It's always possible there's something wrong I'm not aware of. Please let me know if there is!)
Go is pretty much entirely developed in public; there are some Google-internal customizations but none of them are particularly exciting and almost all changes start in the open source repo and are imported from there.
octal_lit = "0" [ "o" | "O" ] [ "_" ] octal_digits .Regular pulse oxygen meters are cheap and reliable.
In the case of encoding/json/v2, enabling GOEXPERIMENT=jsonv2 has two major effects:
1. It flips encoding/json (the original, not /v2) to use the new implementation. This is supposed to be a fully backwards-compatible change, modulo some changes to the text of some errors. We're very interested to hear of any cases of existing programs breaking when the experiment is turned on, because (aside from the aforementioned error text, which you shouldn't be depending on) it likely indicates a bug that needs fixing. This is the "new code, please test" half of the change.
2. It enables the new API (encoding/json/v2, encoding/json/jsontext, some new options in encoding/json). This is the "unstable API, might change in response to feedback" half of the change.
I don't believe this claim is made anywhere.
We've decided that we are not going to make any further attempts to change the syntax of error handling in the foreseeable future. That frees up attention to consider other issues (with errors or otherwise).
Because by and large our female ancestors raised the children, were offered no choice in whether to raise the children, and their opinions of the situation were not recorded.
"git reset --hard" is "...and also change all the files in the working directory to match the new branch commit".
"git reset --soft" is "...but leave the working directory alone".
My big realization was that there’s a lot of macho information there about the care of cast iron, and it’s pretty much all pointless because the stuff is indestructible and the seasoning doesn’t matter much. Every time I make tortillas in a pan the seasoning gets wrecked, and it’s just not a problem. So long as you get the pan to the right temp and have enough fat, nothing sticks regardless of the quality of the seasoning. Skimp on the oil or set the temp too low, and stuff sticks no matter how good the seasoning.
I wash the pans with soap and water (and not too much scrubbing), I never season them deliberately, and they work wonderfully. It’s a very forgiving cooking surface.
DoChan doesn't close the channel because there isn't any reason to do so.
Some background for anyone not familiar with Go errors:
A Go error is an interface value with an Error method that returns the error's text. A simple error can be constructed with the errors.New function:
var ErrNotFound = errors.New("not found")
A nil error indicates success, and a non-nil error indicates some other condition. This is the infamous "if err != nil {}" check. Comparing an error to nil is pretty fast, since it's just a single pointer comparison. On my laptop, it's about 0.5ns. Comparing a bool is about 0.3ns, so "err != nil" is quite a bit slower than "!found", but it's really unlikely the 0.2ns is going to be relevant outside of extremely hot loops.We can also compare an error to some value: "if err == ErrNotFound {}". In this case, we say that ErrNotFound is a "sentinel" (some error value that you compare against). This is about 2.3ns on my laptop; there are two pointer comparisons in this case and a bit more overhead in comparing interface values. (You can actually make this check almost arbitrarily expensive; you could have an error value that's a gigabyte-large array, for example.)
It's common to annotate an error, adding some more useful information to it. For example, we might want our "not found" error to say what was not found:
return fmt.Errorf("%q: not found", name) // "foo": not found
This is quite a bit more expensive than "return ErrNotFound". The fmt.Errorf function will parse a format string, produce the error text, and make two allocations (one for the error string, one for a small struct that holds it). This is about 84ns on my laptop--168 times slower than the fast path! But 84ns is still pretty fast, and you can't get away from the need for at least one allocation if you want to return an error that's varies based on the inputs of the function that produced it. (You can get faster than fmt.Errorf if it matters, but this comment is already getting large.)A problem with using fmt.Errorf in this way is that you can't test the error against a sentinel any more. This was addressed a while back in Go 1.13 with the addition of error wrapping. You can return an error that wraps the sentinel (note the %w format verb):
return fmt.Errorf("%q: %w", name, ErrNotFound) // "foo": not found
And you can then use the errors.Is function to ask whether an error is equal to ErrNotFound, or if it wraps ErrNotFound: if errors.Is(err, ErrNotFound) { ... }
On my laptop, producing a wrapping error like this and testing it with "err != nil" is about 91ns, and testing it with "errors.Is(err, ErrNotFound)" is about 98ns. So using Is is adding 7ns of overhead, which is not nothing, but is also pretty much lost in the noise compared to creating the error in the first place.The example in this blog post went a step further, though, and created an error with not just a single layer of wrapping but one with four. The error text in the wrapped error cases is:
GetValue couldn't get a value: queryValueStore couldn't get a value: queryDisk couldn't get a value: not found
(That is, by the way, a very difficult error to read. Don't hand users errors that look like that.)Creating a stack of four wrapped errors like this on my laptop is 396ns, and inspecting it with errors.Is is another 21ns. 21ns is waaaaay more than the 0.5ns for a simple "err != nil" check, but again the runtime here is massively dominated by the expense of creating the error--which in this case involves repeatedly creating formatted strings and throwing them away, and two allocations for each layer in the stack.
In general, when doing low level optimization of Go code, avoiding allocations is the biggest bang for your buck. If microseconds matter, you absolutely should pay attention to the cost of constructing error values. But the cost of inspecting those values doesn't usually become an issue unless nanoseconds count, and will generally be dominated by the cost of construction.
Also, even the slowest cases here are running about 0.5-1.5μs, which absolutely matters in some cases, but is irrelevant in many others.
(Consider: Horse is five letters, but 馬 is one character. Even at three bytes per character, Chinese wins.)
The term "Stockholm Syndrome" originates from a police consultant inventing a syndrome to diagnose a woman he had never met, in order to discredit her criticism of the largely incompetent police response to her and several other people being taken hostage by a bank robber.
https://www.independent.co.uk/news/world/americas/stockholm-...
(Only “almost” indistinguishable, because it’s possible to decrypt the first packets of the client’s handshake and examine the ALPN parameters used to negotiate an application protocol. And QUIC may be further distinguishable from other UDP traffic through statistical analysis of packet sizes and response latencies, as well as the few unencrypted header bits.)
HTTP/2 and HTTP/3 both have a limit on the number of simultaneous streams (requests) the sender may create. In HTTP/2, the sender may create a new stream immediately after sending a reset for an existing one. In HTTP/3, the receiver is responsible for extending the stream limit after a stream closes, so there is backpressure limiting how quickly the sender may create streams.
This section of the book then concludes with one of the most chilling passages I have ever read in fiction:
“At the hotel we had separate rooms, but in the middle of the night she came sobbing into mine, and we made it up very gently. You see, she had absolutely nowhere else to go.”