HNHacker News
TopNewBestAskShowJobs

kbolino

2,386 karma · joined August 29, 2011

submissionscomments
kbolino··on TSON – A JSON superset with immutable, hash-pinned schemas
Using the query string to carry the sha256 hash but then saying the "hash parameter is verification metadata, not identity" doesn't make much sense. The ?query part of a URL is supposed to be sent to the server. If you want to add client-side (meta)data, you should use the #fragment part of a URL. See RFC 3986, sections 3.4 and 3.5: https://datatracker.ietf.org/doc/html/rfc3986#section-3.4
kbolino··on Go 1.27 Interactive Tour
Rust's solution (the ? operator) is actually applied to the Result type (and, IIRC, Option and ControlFlow). Go does not have a Result type. This makes a 1:1 port of Rust's solution impossible.

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.

kbolino··on Go 1.27 Interactive Tour
You need this in Java because interfaces are explicitly implemented. You don't need this in Go because interfaces are structurally implemented. The way you spell "anything that has a method named Size which returns int" is interface{Size() int}.
kbolino··on Golang proposal: container/: generic collection types
Technically, Java has the same rule, it's just that all user-defined types are reference types, and so the zero value for them is always spelled "null". I'm not sure how Scala pretends otherwise, but I have worked with Kotlin, and non-nullable object fields and method arguments are always a bit of a lie, since the underlying JVM machinery permits, and defaults to, null. (Despite the original authors of Go being unfamiliar with Java, they do seem to have settled on a number of the same decisions.)

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.

kbolino··on Golang proposal: container/: generic collection types
Ignoring the stack trace matter, which is explored in the sibling thread, it seems to me you're really taking issue with two of Go's language design choices that aren't specifically about errors: structural typing and the lack of proper discriminated unions.

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).

kbolino··on Golang proposal: container/: generic collection types
Yes, I don't think stack traces are a silver bullet. They tell you the path taken through the code, but rarely do they tell you why that path was taken. I don't know what Scala stack traces look like specifically, but I've done my fair share of parsing Java stack traces, and they usually just get you in the vicinity of the problem without really telling you the cause. This was best illustrated with the standard exception that was thrown when a hostname failed to resolve, where the message did not contain the hostname. This one was solvable (and did get solved AFAIK), but in a lot of other cases, the relevant context is not available at the depth where the exception is thrown, necessitating exception-wrapping.
kbolino··on Golang proposal: container/: generic collection types
> In the overwhelming majority of cases, you want to trivially send the error up the stack to be handled

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.

kbolino··on DMARC has been public since 2012 but most company domains still don't enforce it
SPF failures overriding DKIM successes is a direct violation of RFC 7489 section 4.2 [1]. I have never observed such behavior in the wild, though my experience may be more limited than yours. There are two possible explanations anyway, one is that the DMARC record was missing or misconfigured, the other is that the DKIM check did not actually succeed even though you had reason to believe it should have (e.g., misaligned sender domain, invalid/stale/rotated key, etc.).

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

kbolino··on DMARC has been public since 2012 but most company domains still don't enforce it
Using both has to be done very carefully, because a positive result from the weaker one (SPF) will override a negative result from the stronger one (DKIM). You should maximally use DKIM and minimally use SPF. Ideally, you should not use SPF at all, but there are some senders that still don't support DKIM.
kbolino··on Blender 5.2 LTS
The audience may be larger, but I don't feel like it's anywhere near "everyone". Personally, I don't know anyone who still uses a dedicated email client except in specific institutional settings. I haven't touched one outside of a corporate office myself in 20 years. Webmail has been good enough for longer than many people have been using computers.
kbolino··on Minecraft: Java Edition now uses SDL3
SDL_RenderGeometry(Raw) are only capable of 2D rendering and the vertexes do not even have a z-coordinate.

Genuine 3D support is available in SDL3 via the GPU types and functions: https://wiki.libsdl.org/SDL3/CategoryGPU

kbolino··on Unity vs. Floating Point
It's quite difficult to find fixed-point number systems that include trigonometry, never mind fancier tools like elliptic integrals. The problem is not just one of static number representations, but also geodetic/geometric computations. [ed: I see this is where the other thread is heading already]
kbolino··on Unity vs. Floating Point
> People are convinced you have to use doubles because of the inaccuracy of floats

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.

kbolino··on Swift at Apple: Migrating the TrueType hinting interpreter
I had this problem on the first Apple Silicon Mac Mini in 2020 so it's at least a little older than 2023.
kbolino··on Swift at Apple: Migrating the TrueType hinting interpreter
It's also the corporate standard for generic cubicle workstation monitors, though it's unusual to find a Mac in such a place anyway.
kbolino··on Go Experiments Explained
I wonder if they can solve the "ecosystem fracture" issue by borrowing the bubble concept from e.g. testing/synctest. The way the synctest API works is by creating a "bubble" around the test harness and the code under test such that the standard library's time package behaves differently, but only for the code running in the bubble.

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.

kbolino··on Windows GOG DOS Games on M-Series Macs
There's another big one, 4K page support. The MMU can be told to set up a virtual address space with smaller, x86-compatible 4096-byte memory pages instead of the default 16384-byte pages.
kbolino··on It's hard to justify buying a Framework 12
The entire existence of 32-bit x86 Macs is in and of itself a tragedy of Apple's own making. Intel shipped the Core 2, a 64-bit CPU widely regarded as one of the company's greatest products, later in the very same year that Apple shipped the first x86 Macs. There was never any reason for 32-bit x86 Macs to exist except for Apple's rush to get them out the door and close down the AIM alliance.
kbolino··on Bijou64: A variable-length integer encoding
For example, you have an envelope format that goes: length prefix in LEB128, message, signature. One party controls the length prefix and signature, a different party controls the message. The message-writing party carefully crafts the message so that, in isolation, it appears innocuous, but when wrapped in the envelope, the first few bytes of the message look like continuation of the length prefix. Best case is the receiving party safely fails to parse the message, worst case is the receiving party successfully parses the message, verifies its digital signature, and interprets it differently than the signer did.
kbolino··on Go: Support for Generic Methods
Generic interfaces already exist. Generic interface methods, which would be relevant, are not planned. The reason is outlined early in the proposal: nobody knows how to implement them efficiently. Rust has the same problem, for what it's worth: dispatchable functions on dyn-compatible traits cannot be generic [1].

[1]: https://doc.rust-lang.org/reference/items/traits.html#r-item...

kbolino··on Migrating from Go to Rust
Indeed, I ran two tests (missing indirect dependency, stale indirect dependency version) and it refused to compile both. Either what I said was never true, or it was only true for earlier versions of the `go` command. Nevertheless, adjusted accordingly, I believe the following statement is true: `go mod tidy` doesn't change versions in go.mod unless it needs to, to satisfy the other dependencies listed in go.mod, or to fill in a missing dependency for an import in code. It would be nice if there were a flag to turn off the latter behavior, though.
kbolino··on Migrating from Go to Rust
There is something you can do to stop it actually. You can use a replace directive, specifying that a module is replaced by itself at a fixed version. See e.g. https://stackoverflow.com/a/77412524/814422

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.

kbolino··on Jank now has its own custom IR
Though there may be other reasons not to use it, invokedynamic is not a new feature of the JVM. If they're targeting 1.8 binary compatibility, they certainly have it at their disposal, since it landed in 1.7.
kbolino··on Linux gaming is faster because Windows APIs are becoming Linux kernel features
I also don't know a whole lot, but yes, they both are ultimately implemented as families of client-server protocols over Unix domain sockets (X11 also supports TCP). Inasmuch as you are content with targeting a single version-set of those protocols, then that is sufficient to enable static compilation.

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...

kbolino··on Linux gaming is faster because Windows APIs are becoming Linux kernel features
I've been intrigued by the possibility of statically compiled games for Linux but I don't think they're the more compatible option. For typical games and players, the game needs to cooperate and interact with the window system. Even setting aside the X11 vs. Wayland issue, AFAIK neither have promised to maintain compatibility for static binaries.
kbolino··on Library for fast mapping of Java records to native memory
There's two problems as I see it.

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.

kbolino··on GeoJSON
For whatever it's worth, you don't have to write anything special to handle the reference system, because the final, published version of RFC 7946 only allows WGS84 anyway.
kbolino··on Bugs Rust won't catch
The other nice thing about rg and fd is that they work natively on Windows.
kbolino··on Bugs Rust won't catch
> Path names are UTF-8-encoded, unrooted, slash-separated sequences of path elements, like “x/y/z”

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...

kbolino··on GitHub Actions is the weakest link
> There is no realistic risk of a SHA collision attack.

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.

← PreviousPage 2 of 34Next →