2,386 karma · joined August 29, 2011
Moreover, the "obvious" solution, i.e. treating (T,error) returns the same as Result<T,E>, does not actually work. The problem is that (T,error) behaves like a product type while Result<T,E> is a sum type and yes, some Go code does (ab)use this. In particular, the io.Reader.Read method in the standard library allows implementers to return (n>0,io.EOF), which has no equivalent Result representation. Many people consider this allowance to be a mistake, but it's too late to change it.
Personally, I'm fine with discriminated unions always allowing "nil" in Go, since IMO the most sensible way to implement them anyway is as a special case of interfaces. Not everyone feels the same way, though.
There is nothing special about an error in Go, and there is no type hierarchy to anchor them against anyway. An error can be a struct{} carrying no value at all, or simple integer with no way to carry context, etc. Error-wrapping is the solution that has been devised to work within these constraints, but there's no obvious way to compose that with the various ideas around reducing boilerplate. For a point of comparison, Rust's anyhow crate was able to square this circle by adding a blanket trait impl for Context, but Go has no equivalent for that. (Also, Rust's ? operator is implemented using unstable components, allowing the details to evolve even while the feature itself is usable in stable Rust, something which also has no equivalent in Go).
As to the lack of discriminated unions, I agree wholeheartedly. There have been some proposals recently to get them into the language, with various levels of conservatism and cohesion with the rest of the language, but a lot of them are faltering on the "every type must have a well defined zero value" issue (yet another design decision that doesn't necessarily have anything to do with errors).
Maybe so, but for JVM languages, this "trivially" includes a stack trace. Go doesn't offer that for normal errors and there was no clear path to getting it. The need for contextual clues when debugging, and the community and then stdlib settling on error-wrapping as the way to do it, makes "simply return err" not a sane default anymore.
I would certainly agree that DKIM is harder to get right. However, the TXT record data size limit is surmountable. You can either use EC algorithms, which have much shorter keys, or stick with e.g. RSA and its very long keys, but span them across multiple 255-byte record data chunks. That having been said, I still think DNS providers should do more to make configuring DKIM easier.
Ultimately, if you have SPF and DKIM set up such that both cover all senders, then you are just using SPF. It is the simpler and more forgiving mechanism, so its broad-scoped successes will always swallow DKIM in practice. The only reason I can think of to do this anyway is if you suspect your email provider will change IPs on you and they don't provide their own SPF record, but if that were the case, they are basically telling you not to use SPF in the first place.
EDIT: RFC 7489 was superseded by RFCs 9989-9901 rather recently. Nevertheless, the definition of success, now given in RFC 9989 section 5.3.5 [2], remains the same.
[1] = https://datatracker.ietf.org/doc/html/rfc7489#section-4.2
[2] = https://datatracker.ietf.org/doc/html/rfc9989#section-5.3.5
Genuine 3D support is available in SDL3 via the GPU types and functions: https://wiki.libsdl.org/SDL3/CategoryGPU
With single-precision floats, considering the worst case, i.e. longitudes close to the equator but far away from the prime meridian, one ulp can translate into as much as 1.68 m (5.5 ft) of distance on the surface (*). That's good enough for some uses, but not for dGPS or any other serious geometric computation. Whereas, with double precision, one ulp in this worst case scenario corresponds to about 3 nanometers. It's overkill, for sure, but if these are the only two types you have, you pick the latter.
* = To represent the integer 179, you need 8 bits, leaving only 16 for the fraction. Since 1 degree of longitude near the equator is about 110 km, you have 1/2^16 degrees * 110 km/degree giving 0.0016784... km.
So, maybe we could also have allocator bubbles, where code running inside the bubble is just as allocator-naive as any other Go code, but when it allocates (make/new/pointer escape), it uses the bubble's allocator instead of the default one.
The big problem I can think of, though, which doesn't apply to time bubbles, would be that pointers drawn from that allocator might escape the bubble (e.g. by assigning them to a longer-lived struct field). It's possibly something that can be detected by the runtime, but because the API is non-invasive by design, it's not something that will be apparent to the programmer when looking at the code.
[1]: https://doc.rust-lang.org/reference/items/traits.html#r-item...
It is worth noting though that, even without such pinning, `go mod tidy` does not update versions willy-nilly. [edit: the following is inaccurate, see grandchild comment] It only syncs go.mod with what is already being used by the build process. In other words, if you see `go mod tidy` change a version, it means that you haven't tidied the file since making other changes to it, and the listing in go.mod was stale with respect to the resolved set of transitive dependencies actually being used.
However, the real issue I'm getting at is the ability to run the same statically compiled binary many years later. That requires a dedication to compatibility (in this case, protocol compatibility). X11 was a bit all over the place on this, though it is probably stable enough now by dint of not getting much attention anymore. It seems like Wayland takes protocol compatibility pretty seriously [1] but there's an important caveat:
[W]hen a protocol transitions from unstable to stable, one last breaking change is permitted.
[ ... ]
Note that many useful protocols are still unstable at the time of writing.
Though this itself may be out of date by now (I can't find a date of authorship in that book).[1]: https://wayland-book.com/protocol-design/design-patterns.htm...
The first is that value types themselves are immutable. This affects code generation and optimization. If you were to modify the value with unmanaged code then you may not observe the modification properly from managed code. Maybe this restriction will get relaxed, but I don't see that on any roadmap any time soon.
The second problem is that value types are still nullable. The flattened array is not going to be identical to a Go slice or a C# Span etc. because it has to track the nullness of each element. It seems they don't want to nail down the exact storage format for that yet, possibly to change it in the future, and possibly because they want to add language-level control over nullability eventually too.
This is only for the "io/fs" package and its generic filesystem abstractions. The "os" package, which always operates on the real filesystem, doesn't actually specify how paths are encoded, nor does its associated helper package "path/filepath".
In practice, non-UTF-8 already wasn't an issue on Unix-like systems, where file paths are natively just byte sequences. You do need to be aware of this possibility to avoid mangling the paths yourself, though. The real problem was Windows, where paths are actually WTF-16, i.e. UTF-16 with unpaired surrogates. Go has addressed this issue by accepting WTF-8 paths since Go 1.21: https://github.com/golang/go/issues/32334#issuecomment-15500...
Indeed. To illustrate why:
1. It is not possible to "retroactively" find a SHA-1 collision for an already known hash. If somebody has produced a SHA-1 hash non-maliciously at any point in the past, it is safe from collisions. This is due to second-preimage resistance, which hasn't been broken for SHA-1 and doesn't seem likely to be broken any time soon.
2. The only way to obtain a SHA-1 collision is to do so knowingly when producing the original hash. You generate a pair of inputs at the same time that both hash to the same value. Certainly, this is an imaginable scenario; e.g. a trusted committer could push one half of the pair wittingly or a reviewer could be fooled into accepting one half of the pair unwittingly, both scenarios creating a timebomb where the malicious actor swaps the commit to the second half of the pair (which presumably carries a malicious payload) later. However, there are two blockers to this approach: Git (not just GitHub) will not accept a commit with a duplicate hash, always sticking with the original one, and GitHub specifically has implemented signature detection for the known SHA-1 collision-generating methods and will reject both halves of such a pair.
In short, there's just no practical way to exploit this weakness of SHA-1 with Git.