zlacker

Go is still not good

submitted by ustad+(OP) on 2025-08-22 09:25:23 | 646 points 825 comments
[view article] [source] [go to bottom]

NOTE: showing posts with links only show all posts
◧◩
20. sgarla+5d[view] [source] [discussion] 2025-08-22 11:42:03
>>baq+Sb
I love Python, but the sheer number of caveats and warnings for __del__ makes me question if this person has ever read the docs [0]. My favorite WTF:

> It is possible (though not recommended!) for the __del__() method to postpone destruction of the instance by creating a new reference to it. This is called object resurrection.

[0]: https://docs.python.org/3/reference/datamodel.html#object.__...

◧◩◪◨⬒
90. gf000+Pg[view] [source] [discussion] 2025-08-22 12:09:24
>>tedk-4+Of
It's rich to complain about verbosity coming from Go.

Nonetheless, Java has eased the psvm requirements, you don't even have to explicitly declare a class and a void main method is enough. [1] Not that it would matter for any non-script code.

[1] https://openjdk.org/jeps/495

◧◩◪◨⬒
91. kasper+Ug[view] [source] [discussion] 2025-08-22 12:09:59
>>tedk-4+Of
For Java 25 which is planned to be released in a couple of weeks:

----- https://openjdk.org/jeps/512 -----

First, we allow main methods to omit the infamous boilerplate of public static void main(String[] args), which simplifies the Hello, World! program to:

  class HelloWorld {
    void main() {
      System.out.println("Hello, World!");
    }
  }
Second, we introduce a compact form of source file that lets developers get straight to the code, without a superfluous class declaration:

  void main() {
    System.out.println("Hello, World!"); 
  }
Third, we add a new class in the java.lang package that provides basic line-oriented I/O methods for beginners, thereby replacing the mysterious System.out.println with a simpler form:

  void main() {
    IO.println("Hello, World!");
  }
103. porrid+vh[view] [source] 2025-08-22 12:15:12
>>ustad+(OP)
I wrote a small explainer on the typed-vs-untyped nil issue. It is one of the things that can actually bite you in production. Easy to miss it in code review.

Here's the accompanying playground: https://go.dev/play/p/Kt93xQGAiHK

If you run the code, you will see that calling read() on ControlMessage causes a panic even though there is a nil check. However, it doesn't happen for Message. See the read() implementation for Message: we need to have a nil check inside the pointer-receiver struct methods. This is the simplest solution. We have a linter for this. The ecosystem also helps, e.g protobuf generated code also has nil checks inside pointer receivers.

◧◩◪
107. termin+Oh[view] [source] [discussion] 2025-08-22 12:18:00
>>thiht+eh
Just below Go with Perl in between. All above Fortran, all below Visual Basic.

https://www.tiobe.com/tiobe-index/

◧◩◪
112. ofrzet+Xh[view] [source] [discussion] 2025-08-22 12:19:17
>>thiht+eh
It's alive and kicking, right? :) https://www.freepascal.org They even have a game engine that can compile to a WASM target: https://castle-engine.io/web
◧◩◪
125. 813ac4+Di[view] [source] [discussion] 2025-08-22 12:22:53
>>termin+3i
I am fine with the subsequent example, too. If you read up about slices, then that's how they are defined and how they work. I am not judging, I am just using the language as it is presented to me.

For anyone interested, this article explains the fundamentals very well, imo: https://go.dev/blog/slices-intro

◧◩◪
129. pjmlp+Oi[view] [source] [discussion] 2025-08-22 12:24:45
>>thiht+eh
Being used by these folks, https://www.embarcadero.com/

If you prefer, I can provide the same example in C, C++, D, Java, C#, Scala, Kotlin, Swift, Rust, Nim, Zig, Odin.

◧◩◪◨⬒
163. theshr+nl[view] [source] [discussion] 2025-08-22 12:36:52
>>Charli+ki
I'm still sad that Silverlight[0] (and Moonlight) died because people hated MS so viscerally back then.

It was actually really good for the time and lightyears ahead of whatever Flash was doing.

But people rather used all kinds of hacks to get Flash working on Linux and OSX rather than use Moonlight.

[0] https://en.wikipedia.org/wiki/Microsoft_Silverlight

◧◩
165. thomas+xl[view] [source] [discussion] 2025-08-22 12:37:56
>>gnfarg+8i
Author here. No, I didn't misunderstand it. Interface variables have two types of nil. Untyped, which does compare to nil, and typed, which does not.

What are you trying to clarify by printing the types? I know what the types are, and that's why I could provide the succinct weird example. I know what the result of the comparisons are, and why.

And the "why" is "because there are two types of nil, because it's a bad language choice".

I've seen this in real code. Someone compares a variable to nil, it's not, and then they call a method (receiver), and it crashes with nil dereference.

Edit, according to this comment this two-types-of-null bites other people in production: >>44983576

◧◩◪◨⬒
194. pjmlp+eo[view] [source] [discussion] 2025-08-22 12:50:55
>>gf000+Yf
You forgot: CLU 1977.

". They are likely the two most difficult parts of any design for parametric polymorphism. In retrospect, we were biased too much by experience with C++ without concepts and Java generics. We would have been well-served to spend more time with CLU and C++ concepts earlier."

https://go.googlesource.com/proposal/+/master/design/go2draf...

◧◩◪◨⬒⬓
208. tracer+Uq[view] [source] [discussion] 2025-08-22 13:04:40
>>gf000+6l
TL;DR: Its impossible to know if anyone on campus has downloaded Oracle Java....

Quote from this article:[1]

     *He told The Register that Oracle is "putting specific Java sales teams in country, and then identifying those companies that appear to be downloading and... then going in and requesting to [do] audits. That recipe appears to be playing out truly globally at this point."*

[1] https://www.theregister.com/2025/06/13/jisc_java_oracle/
◧◩◪
222. toaste+Rt[view] [source] [discussion] 2025-08-22 13:21:15
>>theshr+Ng
Reminded me of this classic talk https://www.youtube.com/watch?v=o9pEzgHorH0
233. singul+kw[view] [source] 2025-08-22 13:33:51
>>ustad+(OP)
That's why there is the Goo language: Go with syntactic sugar and batteries included

https://github.com/pannous/goo/

• errors handled by truthy if or try syntax • all 0s and nils are falsey • #if PORTABLE put(";}") #end • modifying! methods like "hi".reverse!() • GC can be paused/disabled • many more ease of use QoL enhancements

◧◩◪◨⬒
239. doseth+Bz[view] [source] [discussion] 2025-08-22 13:51:18
>>gf000+Am
you shun unnecessary complexity.

If you dont think that exists in java, spend some time in the maven documentation or spring documentation https://docs.spring.io/spring-framework/reference/index.html https://maven.apache.org/guides/getting-started/ Then imagine yourself a beginner to programming trying to make sense of that documentation

you try keep the easy things easy + simple, and try to make the hard things easier and simpler, if possible. Simple aint easy

I dont hate java (anymore), it has plenty of utility, (like say...jira). But when I'm writing golang I pretty much never think "oh I wish this I was writing java right now." no thanks

◧◩◪◨⬒⬓⬔
280. maxdam+EK[view] [source] [discussion] 2025-08-22 14:53:39
>>zozbot+2r
It's never been clear to me where such a type is actually useful. In what cases do you really need to restrict it to valid UTF-8?

You should always be able to iterate the code points of a string, whether or not it's valid Unicode. The iterator can either silently replace any errors with replacement characters, or denote the errors by returning eg, `Result<char, Utf8Error>`, depending on the use case.

All languages that have tried restricting Unicode afaik have ended up adding workarounds for the fact that real world "text" sometimes has encoding errors and it's often better to just preserve the errors instead of corrupting the data through replacement characters, or just refusing to accept some inputs and crashing the program.

In Rust there's bstr/ByteStr (currently being added to std), awkward having to decide which string type to use.

In Python there's PEP-383/"surrogateescape", which works because Python strings are not guaranteed valid (they're potentially ill-formed UTF-32 sequences, with a range restriction). Awkward figuring out when to actually use it.

In Raku there's UTF8-C8, which is probably the weirdest workaround of all (left as an exercise for the reader to try to understand .. oh, and it also interferes with valid Unicode that's not normalized, because that's another stupid restriction).

Meanwhile the Unicode standard itself specifies Unicode strings as being sequences of code units [0][1], so Go is one of the few modern languages that actually implements Unicode (8-bit) strings. Note that at least two out of the three inventors of Go also basically invented UTF-8.

[0] https://www.unicode.org/versions/Unicode16.0.0/core-spec/cha...

> Unicode string: A code unit sequence containing code units of a particular Unicode encoding form.

[1] https://www.unicode.org/versions/Unicode16.0.0/core-spec/cha...

> Unicode strings need not contain well-formed code unit sequences under all conditions. This is equivalent to saying that a particular Unicode string need not be in a Unicode encoding form.

◧◩◪◨⬒⬓⬔
315. tuetuo+mQ[view] [source] [discussion] 2025-08-22 15:23:35
>>tracer+Sw
Sorry but for most programming tasks I prefer having actual data containers with features than an HTTP library: Set, Tree, etc types. Those are fundamental CS building blocks yet are absent from the Go standard library. (well, they were added pretty recently, still nowhere near as featureful than std::collection in Rust).

Also, as mentioned by another comment, an HTTP or crypto library can become obsolete _fast_. What about HTTP3? What about post-quantum crypto? What about security fixes? The stdlib is tied to the language version, thus to a language release. Having such code independant allows is to evolve much faster, be leaner, and be more composable. So yes, the library is well maintained, but it's tied to the Go version.

Also, it enables breaking API changes if absolutely needed. I can name two precendents:

- in rust, time APIs in chrono had to be changed a few times, and the Rust maintainers were thankful it was not part of the stdlib, as it allowed massive changes

- otoh, in Go, it was found out that net.Ip has an absolutely atrocious design (it's just an alias for []byte). Tailscale wrote a replacement that's now in a subpackage in net, but the old net.Ip is set in stone. (https://tailscale.com/blog/netaddr-new-ip-type-for-go)

◧◩◪◨⬒⬓
330. mdanie+iS[view] [source] [discussion] 2025-08-22 15:34:14
>>nkozyr+fL
I'm not OP, but I also got tripped up the first time I saw time.Parse("2006-01-02 03:04:05") and was like what the actual?!

https://pkg.go.dev/time#Layout

◧◩
336. theobe+1T[view] [source] [discussion] 2025-08-22 15:38:51
>>openas+TK
Perhaps the new "Green Tea" GC will help? It's described as "a parallel marking algorithm that, if not memory-centric, is at least memory-aware, in that it endeavors to process objects close to one another together."

https://github.com/golang/go/issues/73581

◧◩◪◨⬒
342. mdanie+JT[view] [source] [discussion] 2025-08-22 15:41:24
>>toaste+ut
I know, both proprietary and enterprise, right? https://github.com/JetBrains/intellij-community/blob/idea/20... (I would also link to the Apache 2 copy of PyCharm but it wouldn't matter to folks who just enjoy shitting on professional tools)
◧◩◪◨⬒
360. ducker+iV[view] [source] [discussion] 2025-08-22 15:49:53
>>klodol+nG
The big problem isn't invalid UTF-8 but invalid UTF-16 (on Windows et al). AIUI Go had nasty bugs around this (https://github.com/golang/go/issues/59971) until it recently adopted WTF-8, an encoding that was actually invented for Rust's OsStr.

WTF-8 has some inconvenient properties. Concatenating two strings requires special handling. Rust's opaque types can patch over this but I bet Go's WTF-8 handling exposes some unintuitive behavior.

There is a desire to add a normal string API to OsStr but the details aren't settled. For example: should it be possible to split an OsStr on an OsStr needle? This can be implemented but it'd require switching to OMG-WTF-8 (https://rust-lang.github.io/rfcs/2295-os-str-pattern.html), an encoding with even more special cases. (I've thrown my own hat into this ring with OsStr::slice_encoded_bytes().)

The current state is pretty sad yeah. If you're OK with losing portability you can use the OsStrExt extension traits.

◧◩◪
383. mdanie+GY[view] [source] [discussion] 2025-08-22 16:06:23
>>hellco+DP
or go ahead and commit it, if you're galaxy brain and want to throw off would-be attackers trying to understand your codebase https://github.com/pulumi/pulumi/blob/v3.191.0/pkg/go.mod#L5 or https://github.com/opentofu/terraform-provider-aws/blob/main...
◧◩◪◨⬒
407. Jtsumm+J11[view] [source] [discussion] 2025-08-22 16:22:14
>>antonc+W01
Erlang, Elixir, Ada, plenty of others. Erlang and Ada predate Go by several decades, too.

You wanted sources, here's the chapter on tasks and synchronization in the Ada LRM: http://www.ada-auth.org/standards/22rm/html/RM-9.html

For Erlang and Elixir, concurrent programming is pretty much their thing so grab any book or tutorial on them and you'll be introduced to how they handle it.

◧◩◪◨⬒⬓⬔⧯▣
415. maxdam+g31[view] [source] [discussion] 2025-08-22 16:30:13
>>xyzzyz+iW
But there's no option to just construct the string with the invalid bytes. 3) is not for this purpose; it is for when you already know that it is valid.

If you use 3) to create a &str/String from invalid bytes, you can't safely use that string as the standard library is unfortunately designed around the assumption that only valid UTF-8 is stored.

https://doc.rust-lang.org/std/primitive.str.html#invariant

> Constructing a non-UTF-8 string slice is not immediate undefined behavior, but any function called on a string slice may assume that it is valid UTF-8, which means that a non-UTF-8 string slice can lead to undefined behavior down the road.

◧◩◪◨
431. blibbl+p41[view] [source] [discussion] 2025-08-22 16:37:21
>>stouse+mU
> Golang makes it easy to do the dumb, wrong, incorrect thing that looks like it works 99.7% of the time. How can that be wrong? It works in almost all cases!

my favorite example of this was the go authors refusing to add monotonic time into the standard library because they confidently misunderstood its necessity

(presumably because clocks at google don't ever step)

then after some huge outages (due to leap seconds) they finally added it

now the libraries are a complete a mess because the original clock/time abstractions weren't built with the concept of multiple clocks

and every go program written is littered with terrible bugs due to use of the wrong clock

https://github.com/golang/go/issues/12914 (https://github.com/golang/go/issues/12914#issuecomment-15075... might qualify for the worst comment ever)

◧◩◪◨⬒
432. mhink+u41[view] [source] [discussion] 2025-08-22 16:37:48
>>nothra+i11
Not to dispute too strongly (since I haven't used this functionality myself), but Node.js does have support for true multithreading since v12: https://nodejs.org/dist/latest/docs/api/worker_threads.html. I'm not sure what you mean by "M:1 threaded" but I'm legitimately curious to understand more here, if you're willing to give more details.

There are also runtimes like e.g. Hermes (used primarily by React Native), there's support for separating operations between the graphics thread and other threads.

All that being said, I won't dispute OP's point about "handling concurrency [...] within the language"- multithreading and concurrency are baked into the Golang language in a more fundamental way than Javascript. But it's certainly worth pointing out that at least several of the major runtimes are capable of multithreading, out of the box.

◧◩◪◨⬒
441. TheDon+p51[view] [source] [discussion] 2025-08-22 16:42:25
>>morsec+m11
The generics are a weak mimicry of what generics could be, almost as if to say "there we did it" without actually making the language that much more expressive.

For example, you're not allowed to write the following:

    type Option[T any] struct { t *T }

    func (o *Option[T]) Map[U any](f func(T) U) *Option[U] { ... }
That fails because methods can't have type parameters, only structs and functions. It hurts the ergonomics of generics quite a bit.

And, as you rightly point out, the stdlib is largely pre-generics, so now there's a bunch of duplicate functions, like "strings.Sort" and "slices.Sort", "atomic.Pointer" and "atomic.Value", quite possible a sync/v2 soon https://github.com/golang/go/issues/71076, etc.

The old non-generic versions also aren't deprecated typically, so they're just there to trap people that don't know "no never use atomic.Value, always use atomic.Pointer".

◧◩◪◨⬒⬓
444. odo124+z51[view] [source] [discussion] 2025-08-22 16:43:53
>>mhink+u41
I had to look M:1 threading up too - it's this: https://en.wikipedia.org/wiki/Thread_(computing)#M:1_(user-l...

Basically OP was saying that JavaScript can run multiple tasks concurrently, but with no parallelism since all tasks map to 1 OS thread.

◧◩◪◨⬒⬓⬔⧯
446. mdanie+P51[view] [source] [discussion] 2025-08-22 16:44:36
>>pjmlp+wT
Well, that's the problem I was highlighting - golang somehow decided to have the worst of both worlds: arbitrary domains in import paths and then putting the actual ref of the source code ... elsewhere

  import "gopkg.in/yaml.v3" // does *what* now?

  curl https://gopkg.in/yaml.v3?go-get=1 | grep github
  <meta name="go-source" content="gopkg.in/yaml.v3 _ https://github.com/go-yaml/yaml/tree/v3.0.1{/dir} https://github.com/go-yaml/yaml/blob/v3.0.1{/dir}/{file}#L{line}">
oh, ok :-/

I would presume only a go.mod entry would specify whether it really is v3.0.0 or v3.0.1

Also, for future generations, don't use that package https://github.com/go-yaml/yaml#this-project-is-unmaintained

◧◩
448. laserl+u61[view] [source] [discussion] 2025-08-22 16:48:09
>>tapirl+8U
In a comment in this thread, the author states that they have 12 - 15 years of experience in Go [0].

[0] >>44985378

◧◩◪◨⬒⬓⬔⧯
470. odo124+591[view] [source] [discussion] 2025-08-22 16:59:29
>>dboreh+D81
No. See [Concurrency vs. Parallelism](https://stackoverflow.com/questions/1050222/what-is-the-diff...).

The tasks run concurrently, but not in parallel.

◧◩◪◨⬒⬓
479. cogman+ja1[view] [source] [discussion] 2025-08-22 17:04:50
>>clumsy+gV
Still an issue. The main problem is for native compilation you have to declare your reflection targets upfront. That can be a headache if your framework doesn't support it.

You can get a large portion of what graal native offers by using AppCDS and compressed object headers.

Here's the latest JEP for all that.

https://openjdk.org/jeps/483

◧◩◪◨
492. bob102+wb1[view] [source] [discussion] 2025-08-22 17:12:19
>>acedTr+UW
Just because you can learn about something doesn't mean you need to. C# now offers top-level programs that are indistinguishable from python scripts at a quick glance. No namespaces, classes or main methods are required. Just the code you want to execute and one simple file.

https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals...

◧◩◪◨⬒⬓
517. stevek+vf1[view] [source] [discussion] 2025-08-22 17:34:11
>>tialar+Ov
It wouldn't have been an alias, it would have been struct Str([u8]). Nothing would have been different about the safety story.

https://github.com/rust-lang/rfcs/issues/2692

◧◩◪◨
529. rollca+7i1[view] [source] [discussion] 2025-08-22 17:48:17
>>pjmlp+Rm
That's a bit unfair to the modern compilers - there's a lot more standards to adhere to, more (micro)architectures, frontends need to plug into IRs into optimisers into codegen, etc. Some of it is self-inflicted: do you need yet-another 0.01% optimisation? At the cost of maintainability, or even correctness? (Hello, UB.) But most of it is just computers evolving.

But those are not rules. If you're doing stuff for fun, check out QBE <https://c9x.me/compile/> or Plan 9 C <https://plan9.io/sys/doc/comp.html> (which Go was derived from!)

◧◩◪◨
550. kbolin+kq1[view] [source] [discussion] 2025-08-22 18:27:29
>>gf000+g91
It is rare to encounter this in practice, and it does get picked up by the race detector (which you have to consciously enable). But the language designers chose not to address it, so I think it's a valid criticism. [1]

Once you know about it, though, it's easy to avoid. I do think, especially given that the CSP features of Go are downplayed nowadays, this should be addressed more prominently in the docs, with the more realistic solutions presented (atomics, mutexes).

It could also potentially be addressed using 128-bit atomics, at least for strings and interfaces (whereas slices are too big, taking up 3 words). The idea of adding general 128-bit atomic support is on their radar [2] and there already exists a package for it [3], but I don't think strings or interfaces meet the alignment requirements.

[1]: https://research.swtch.com/gorace

[2]: https://github.com/golang/go/issues/61236

[3]: https://pkg.go.dev/github.com/CAFxX/atomic128

◧◩◪◨⬒⬓⬔⧯
610. Mawr+cN1[view] [source] [discussion] 2025-08-22 20:24:32
>>tuetuo+mQ
> Set, Tree, etc types. Those are fundamental CS building blocks

And if you're engaging in CS then Go is probably the last language you should be using. If however, what you're interested in doing is programming, the fundamental data structures there are arrays and hashmaps, which Go has built-in. Everything else is niche.

> Also, as mentioned by another comment, an HTTP or crypto library can become obsolete _fast_. What about HTTP3? What about post-quantum crypto? What about security fixes? The stdlib is tied to the language version, thus to a language release. Having such code independant allows is to evolve much faster, be leaner, and be more composable. So yes, the library is well maintained, but it's tied to the Go version.

The entire point is to have a well supported crypto library. Which Go does and it's always kept up to date. Including security fixes.

As for post-quantum: https://words.filippo.io/mlkem768/

> - otoh, in Go, it was found out that net.Ip has an absolutely atrocious design (it's just an alias for []byte). Tailscale wrote a replacement that's now in a subpackage in net, but the old net.Ip is set in stone. (https://tailscale.com/blog/netaddr-new-ip-type-for-go)

Yes, and? This seems to me to be the perfect way to handle things - at all times there is a blessed high-quality library to use. As warts of its design get found out over time, a new version is worked on and released once every ~10 years.

A total mess of barely-supported libraries that the userbase is split over is just that - a mess.

◧◩◪
612. kragen+9O1[view] [source] [discussion] 2025-08-22 20:28:47
>>thomas+fK1
Oh, I meant that you were mistaken about handling nom-UTF-8 filenames (see >>44986040 ) and in 90% of cases a deferred mutex unlock makes things worse instead of better.

The kind of general reason you need a mutex is that you are mutating some data structure from one valid state to another, but in between, it's in an inconsistent state. If other code sees that inconsistent state, it might crash or otherwise misbehave. So you acquire the mutex beforehand and release it once it's in the new valid state.

But what happens if you panic during the modification? The data structure might still be in an inconsistent state! But now it's unlocked! So other threads that use the inconsistent data will misbehave, and now you have a very tricky bug to fix.

This doesn't always apply. Maybe the mutex is guarding a more systemic consistency condition, like "the number in this variable is the number of messages we have received", and nothing will ever crash or otherwise malfunction if some counts are lost. Maybe it's really just providing a memory fence guarding against a torn read. Maybe the mutex is just guarding a compare-and-swap operation written out as a compare followed by a swap. But in cases like these I question whether you can really panic with the mutex held!

This is why Java deprecated Thread.stop. (But Java does implicitly unlock mutexes when unwinding during exception handling, and that does cause bugs.)

This is only vaguely relevant to your topic of whether Golang is good or not. Explicit error handling arguably improves your chances of noticing the possibility of an error arising with the mutex held, and therefore handling it correctly, but you correctly pointed out that because Go does have exceptions, you still have to worry about it. And cases like these tend to be fiendishly hard to test—maybe it's literally impossible for a test to make the code you're calling panic.

◧◩
622. metalt+DS1[view] [source] [discussion] 2025-08-22 20:57:38
>>bitdee+kH1
> Go’s design caused a production issue

A simple one, if you create two separate library in Go and try to link with an application, you will have a terrible time.

I've ran into this same issue: https://github.com/golang/go/issues/65050

https://www.youtube.com/watch?v=xuv9A7CJF54&t=440s

◧◩◪◨⬒
627. Mawr+4V1[view] [source] [discussion] 2025-08-22 21:12:02
>>gf000+Am
> I can reasonably likely run a 30 years old compiled, .jar file on the latest Java version.

Great, pretty much every language ever can do the equivalent. Not what anyone is talking about.

> Java is the epitome of backwards and forward-compatible changes,

Is the number of companies stuck on Java 8 lower than 50% yet? [1]

[1]: https://www.jetbrains.com/lp/devecosystem-2023/java/

◧◩◪
635. Mawr+hY1[view] [source] [discussion] 2025-08-22 21:31:53
>>whales+Bf1
> Would love to see a coffeescript for golang.

It's not viable to use, but: https://github.com/borgo-lang/borgo

◧◩
649. Mawr+W32[view] [source] [discussion] 2025-08-22 22:05:56
>>abtinf+Qu
Yep, most of what the author complains about are trivial issues you could find in any language. For contrast, some real, deep-rooted language design problems with Go are:

- Zero values, lack of support for constructors

- Poor handling of null

- Mutability by default

- A static type system not designed with generics in mind

- `int` is not arbitrary precision [1]

- The built-in array type (slices) has poorly considered ownership semantics [2]

Notable mentions:

- No sum types

- No string interpolation

[1]: https://github.com/golang/go/issues/19623

[2]: >>39477821

◧◩◪
651. Mawr+s42[view] [source] [discussion] 2025-08-22 22:08:45
>>theshr+Ng
Sure have: https://youtu.be/wf-BqAjZb8M?t=831
◧◩◪◨⬒
677. pas+am2[view] [source] [discussion] 2025-08-23 00:06:40
>>klodol+nG
It's completely in-line with Rust's approach. Concentrate on the hard stuff that lifts every boat. Like the type system, language features, and keep the standard library very small, and maybe import/adopt very successful packages. (Like once_cell. But since removing things from std is considered a forever no-no, it seems path handling has to be solved by crates. Eg. https://github.com/chipsenkbeil/typed-path )
◧◩◪◨⬒⬓⬔⧯▣▦▧▨◲◳⚿
681. kragen+xn2[view] [source] [discussion] 2025-08-23 00:18:34
>>xyzzyz+g42
Well, for example, the extremely exotic scenario of passing command-line arguments to a program on little-known operating systems like Linux and FreeBSD; https://doc.rust-lang.org/book/ch12-01-accepting-command-lin... recommends:

  use std::env;

  fn main() {
      let args: Vec<String> = env::args().collect();
      ...
  }
When I run this code, a literal example from the official manual, with this filename I have here, it panics:

    $ ./main $'\200'
    thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: "\x80"', library/std/src/env.rs:805:51
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
($'\200' is bash's notation for a single byte with the value 128. We'll see it below in the strace output.)

So, literally any program anyone writes in Rust will crash if you attempt to pass it that filename, if it uses the manual's recommended way to accept command-line arguments. It might work fine for a long time, in all kinds of tests, and then blow up in production when a wild file appears with a filename that fails to be valid Unicode.

This C program I just wrote handles it fine:

  #include <unistd.h>
  #include <fcntl.h>
  #include <stdio.h>
  #include <stdlib.h>

  char buf[4096];

  void
  err(char *s)
  {
    perror(s);
    exit(-1);
  }

  int
  main(int argc, char **argv)
  {
    int input, output;
    if ((input = open(argv[1], O_RDONLY)) < 0) err(argv[1]);
    if ((output = open(argv[2], O_WRONLY | O_CREAT, 0666)) < 0) err(argv[2]);
    for (;;) {
      ssize_t size = read(input, buf, sizeof buf);
      if (size < 0) err("read");
      if (size == 0) return 0;
      ssize_t size2 = write(output, buf, (size_t)size);
      if (size2 != size) err("write");
    }
  }
(I probably should have used O_TRUNC.)

Here you can see that it does successfully copy that file:

    $ cat baz
    cat: baz: No such file or directory
    $ strace -s4096 ./cp $'\200' baz
    execve("./cp", ["./cp", "\200", "baz"], 0x7ffd7ab60058 /* 50 vars */) = 0
    brk(NULL)                               = 0xd3ec000
    brk(0xd3ecd00)                          = 0xd3ecd00
    arch_prctl(ARCH_SET_FS, 0xd3ec380)      = 0
    set_tid_address(0xd3ec650)              = 4153012
    set_robust_list(0xd3ec660, 24)          = 0
    rseq(0xd3ecca0, 0x20, 0, 0x53053053)    = 0
    prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=9788*1024, rlim_max=RLIM64_INFINITY}) = 0
    readlink("/proc/self/exe", ".../cp", 4096) = 22
    getrandom("\xcf\x1f\xb7\xd3\xdb\x4c\xc7\x2c", 8, GRND_NONBLOCK) = 8
    brk(NULL)                               = 0xd3ecd00
    brk(0xd40dd00)                          = 0xd40dd00
    brk(0xd40e000)                          = 0xd40e000
    mprotect(0x4a2000, 16384, PROT_READ)    = 0
    openat(AT_FDCWD, "\200", O_RDONLY)      = 3
    openat(AT_FDCWD, "baz", O_WRONLY|O_CREAT, 0666) = 4
    read(3, "foo\n", 4096)                  = 4
    write(4, "foo\n", 4)                    = 4
    read(3, "", 4096)                       = 0
    exit_group(0)                           = ?
    +++ exited with 0 +++
    $ cat baz
    foo
The Rust manual page linked above explains why they think introducing this bug by default into all your programs is a good idea, and how to avoid it:

> Note that std::env::args will panic if any argument contains invalid Unicode. If your program needs to accept arguments containing invalid Unicode, use std::env::args_os instead. That function returns an iterator that produces OsString values instead of String values. We’ve chosen to use std::env::args here for simplicity because OsString values differ per platform and are more complex to work with than String values.

I don't know what's "complex" about OsString, but for the time being I'll take the manual's word for it.

So, Rust's approach evidently makes it extremely hard not to introduce problems like that, even in the simplest programs.

Go's approach doesn't have that problem; this program works just as well as the C program, without the Rust footgun:

  package main

  import (
          "io"
          "log"
          "os"
  )

  func main() {
          src, err := os.Open(os.Args[1])
          if err != nil {
                  log.Fatalf("open source: %v", err)
          }

          dst, err := os.OpenFile(os.Args[2], os.O_CREATE|os.O_WRONLY, 0666)
          if err != nil {
                  log.Fatalf("create dest: %v", err)
          }

          if _, err := io.Copy(dst, src); err != nil {
                  log.Fatalf("copy: %v", err)
          }
  }
(O_CREATE makes me laugh. I guess Ken did get to spell "creat" with an "e" after all!)

This program generates a much less clean strace, so I am not going to include it.

You might wonder how such a filename could arise other than as a deliberate attack. The most common scenario is when the filenames are encoded in a non-Unicode encoding like Shift-JIS or Latin-1, followed by disk corruption, but the deliberate attack scenario is nothing to sneeze at either. You don't want attackers to be able to create filenames your tools can't see, or turn to stone if they examine, like Medusa.

Note that the log message on error also includes the ill-formed Unicode filename:

  $ ./cp $'\201' baz
  2025/08/22 21:53:49 open source: open ζ: no such file or directory
But it didn't say ζ. It actually emitted a byte with value 129, making the error message ill-formed UTF-8. This is obviously potentially dangerous, depending on where that logfile goes because it can include arbitrary terminal escape sequences. But note that Rust's UTF-8 validation won't protect you from that, or from things like this:

  $ ./cp $'\n2025/08/22 21:59:59 oh no' baz
  2025/08/22 21:59:09 open source: open 
  2025/08/22 21:59:59 oh no: no such file or directory
I'm not bagging on Rust. There are a lot of good things about Rust. But its string handling is not one of them.
◧◩◪◨⬒⬓⬔⧯▣▦▧▨◲◳⚿⛋⬕
682. kragen+Kn2[view] [source] [discussion] 2025-08-23 00:20:25
>>adastr+vi2
You can't get null bytes from a command-line argument. And going by >>44991638 it's common to not use OsString when accepting command-line arguments, because std::env::args yields Strings, which means that probably most Rust programs that accept filenames on the command line have this bug.
◧◩◪◨⬒⬓⬔⧯▣▦▧▨◲◳
684. kragen+Ep2[view] [source] [discussion] 2025-08-23 00:40:33
>>maxdam+Ei2
It's much worse than that—in many cases, such as passing a filename to a program on the Linux command line, correct behavior requires not validating, so erroring out when validation fails introduces bugs. I've explained this in more detail in >>44991638 .
◧◩◪◨⬒⬓⬔⧯▣▦▧
688. maxdam+Ns2[view] [source] [discussion] 2025-08-23 01:08:58
>>adastr+M41
> I don’t understand this complaint. (3) sounds like exactly what you are asking for. And yes, doing unsafe thing is unsafe

You're meant to use `unsafe` as a way of limiting the scope of reasoning about safety.

Once you construct a `&str` using `from_utf8_unchecked`, you can't safely pass it to any other function without looking at its code and reasoning about whether it's still safe.

Also see the actual documentation: https://doc.rust-lang.org/std/primitive.str.html#method.from...

> Safety: The bytes passed in must be valid UTF-8.

◧◩◪◨⬒⬓⬔⧯
698. kragen+9A2[view] [source] [discussion] 2025-08-23 02:16:42
>>kragen+PC1
As I demonstrated in >>44991638 , it's easy to run into this problem in, for example, Rust.
◧◩◪◨⬒⬓⬔⧯▣
720. xyzzyz+8J2[view] [source] [discussion] 2025-08-23 03:49:54
>>maxdam+su2
Imagine that you're writing a function that'll walk the directory to copy some files somewhere else, and then delete the directory. Unfortunately, you hit this

https://github.com/golang/go/issues/32334

oops, looks like some files are just inaccessible to you, and you cannot copy them.

Fortunately, when you try to delete the source directory, Go's standard library enters infinite loop, which saves your data.

https://github.com/golang/go/issues/59971

◧◩◪◨⬒⬓
736. hnlmor+F33[view] [source] [discussion] 2025-08-23 08:10:09
>>Too+mS2
That's a theoretical problem that almost never surfaces in practice.

Using `defer...recover` is computationally expensive within hot paths. And since Go encourages errors to be surfaced via the `error` type, when writing idomatic Go you don't actually need to raise exceptions all that often.

So panics are reserved for instances where your code reaches a point that it cannot reasonably continue.

This means you want to catch panics at boundary points in your code.

Given that global state is an anti-pattern in any language, you'd want to wrap your mutex, file, whatever operations in a `struct` or its own package and instantiate it there. So you can have a destructor on that which is still caught by panic and not overly reliant on `defer` to achieve it.

This actually leads to my biggest pet peeve in Go. It's not `x, err := someFunction()` and nor is it `defer/panic`, these are all just ugly syntax that doesn't actually slow you down. My biggest complaint is the lack of creator and destructor methods for structs.

the `NewClass`-style way of initialising types is an ugly workaround and it constantly requires checking if libraries require manual initilisation before use. Not a massive time sink but it's not something the IDE can easily hint to you so you do get pulled from your flow to then either Google that library or check what `New...` functions are defined in the IDEs syntax completion. Either way, it's a distraction.

The lack of a destructor, though, does really show up all the other weaknesses in Go. It then makes `defer` so much more important than it otherwise should be. It means the language then needs runtime hacks for you to add your own dereference hooks[0][1] (this is a problem I run into often with CGO where I do need to deallocate, for example, texture data from the GPU). And it means you need to check each struct to see if it includes a `Close()` method.

I've heard the argument against destructors is that they don't catch errors. But the counterargument to that is the `defer x.Close()` idiom, where errors are ignored anyway.

I think that change, and tuples too so `err` doesn't always have to be its own variable, would transform Go significantly into something that feels just as ergonomic as it is simple to learn, and without harming the readability of Go code.

[0] https://medium.com/@ksandeeptech07/improved-finalizers-in-go...

[1] eg https://github.com/lmorg/ttyphoon/blob/main/window/backend/r...

◧◩◪◨⬒⬓
760. kragen+YC3[view] [source] [discussion] 2025-08-23 14:55:05
>>yencab+fV
I've posted a longer explanation in >>44991638 . I'm interested to hear which kernel and which firesystem zimpenfish is using that has this problem.
◧◩
791. looshc+zb5[view] [source] [discussion] 2025-08-24 08:27:09
>>kstene+R91
> Debugging is a nightmare because it refuses to even compile if you have unused X (which you always will have when you're debugging and testing "What happens if I comment out this bit?")

i made a cli tool[1] to mitigate this problem, it can be integrated into an ide quite easily, currently it has (neo)vim integration described in the readme and a vs code plugin-companion [2] which both can serve as an example and inspiration for creating an integration for your ide of choice

[1] https://github.com/vipkek/gouse

[2] https://marketplace.visualstudio.com/items?itemName=looshch....

edit: formatting

◧◩◪
793. looshc+Xb5[view] [source] [discussion] 2025-08-24 08:31:25
>>AtNigh+iz1
i had similar experience when i created a tool to solve this problem which i mentioned[1] earlier and posted[2] it to golang-nuts, people told me it’s horrible and you are fine doing what my tool does manually

[1] https://news.ycombinator.com/item?id=44982491#44986946

[2] https://groups.google.com/g/golang-nuts/c/QL5h8zO7MDo/m/qiLi...

◧◩
797. looshc+6d5[view] [source] [discussion] 2025-08-24 08:42:26
>>softwa+Hb
> my main annoyance is deciding when to use a pointer or not use a pointer as variable/receiver/argument

i think you can take these[1][2][3][4] official advices and extrapolate to other cases

[1] https://go.dev/wiki/CodeReviewComments#receiver-type

[2] https://google.github.io/styleguide/go/decisions#receiver-ty...

[3] https://go.dev/doc/effective_go#pointers_vs_values

[4] https://go.dev/doc/faq#methods_on_values_or_pointers

◧◩◪◨⬒
805. rustan+zH6[view] [source] [discussion] 2025-08-24 22:11:52
>>termin+hj
Ownership is perfectly consistent.

`append` always returns a new slice value

`func append(slice []Type, elems ...Type) []Type`

The only correct way to use append is something like `sl = append(sl, 1, 2, 3)`

`sl` is now a new slice value, as `append` always returns a new slice value. You must now pass the new slice value back to the user, and the user must use the new slice value. The user must not use the old slice value.

It's trivial to fix the "bug" in the article, once you actually understand what a slice value is: https://go.dev/play/p/JRMV_IuXcZ6

A slice is not the underlying array, and the underlying array is not the slice. A slice is just a box with 3 numbers.

◧◩◪◨⬒⬓⬔
810. naikro+RY7[view] [source] [discussion] 2025-08-25 12:35:50
>>TheDon+xY
Go programmers (and `range`) assume that string is always valid UTF-8 but there is no guarantee by the language that a string is valid UTF-8. The string itself is still a []byte. `range` sees the `string` type and has special handling for strings that it does not have when it ranges over []byte. Recall that aliased types are not viewed as the same type at any time.

A couple quotes from the Go Blog by Rob Pike:

> It’s important to state right up front that a string holds arbitrary bytes. It is not required to hold Unicode text, UTF-8 text, or any other predefined format. As far as the content of a string is concerned, it is exactly equivalent to a slice of bytes.

> Besides the axiomatic detail that Go source code is UTF-8, there’s really only one way that Go treats UTF-8 specially, and that is when using a for range loop on a string.

Both from https://go.dev/blog/strings

If you want UTF-8 in a guaranteed way, use the functions available in unicode/utf8 for that. Using `string` is not sufficient unless you make sure you only put UTF-8 into those strings.

If you put valid UTF-8 into a string, you can be sure that the string holds valid UTF-8, but if someone else puts data into a string, and you assume that it is valid UTF-8, you may have a problem because of that assumption.

◧◩◪◨⬒
812. cogman+iW8[view] [source] [discussion] 2025-08-25 18:06:26
>>gf000+L13
I don't think that's a mistake. Besides the promise of memory management the "write once run everywhere" promise was a huge selling point of the JVM. So much so that Bill gates famously was terrified of it.

"I am literally losing sleep over this issue since together with a move to more server based applications it seems like it could make it easy for people to do competitive operating systems." [1]

[1] https://www.joyk.com/dig/detail/1672957813119759#gsc.tab=0

◧◩◪◨⬒
815. simon_+B9b[view] [source] [discussion] 2025-08-26 12:04:13
>>lisbbb+TC2
there are two ways to achieve native binaries with Kotlin: 1) Kotlin native https://kotlinlang.org/docs/native-overview.html 2) Kotlin JVM but use GraalVM https://www.graalvm.org/ (which creates a binary of your program combined with GraalVM)
◧◩◪
818. baranu+6Bb[view] [source] [discussion] 2025-08-26 14:30:55
>>Mawr+W32
There are other choices of languages, that are close to and influenced by Golang. Languages such as Vlang[1] (which addresses several issues mentioned) and maybe Odin[2]. Even more, they are at the stage where advance programmers can contribute or influence them in the ways that they might find satisfactory.

Golang is too far down the road and cemented in its ways, to expect such significant changes in direction. At this stage, people need to accept it for what it is or look elsewhere.

[1]: https://vlang.io/

[2]: https://odin-lang.org/

[go to top]