AND unlike Rust, Go has an eminently usable and comprehensive stdlib.
It means that for most projects you can just Get Stuff Done (TM) instead of deciding which of 24 "reinventing the wheel" Rust Crates you want to use for X, Y and Z functions of your Rust binary.
I can probably easily understand borrowing. It's mostly an issue of controlling pointer aliasing wrt mutability, especially in a multithreaded context I guess.
But that gottdarn syntax... And goroutines are too nice for the type of code I use.
I'm too spoiled with Go I guess.
Maybe 10 more years.
type Kind enum {
Simple,
Complex,
Emacs,
}
const kindStrings [Kind]string = {
"simple",
"complex",
"emacs",
}
func (k Kind) String() string {
return kindStrings[k]
}
func t() {
var a = Kind.Emacs - Kind.Simple // a has type int and value 2
var b = Kind.Simple + Kind.Emacs // type error
var c = Kind.Simple + 1 // type error
var d = len(Kind) // d has type int and value 3
for k := range Kind {
fmt.Printf("%v\n", k) // prints what you expect it to do
}
// not sure about legality or runtime behaviour of the followng
var t = Kind.Emacs
t++
t = Kind(42)
}
Would be nice, not gonna lie.The parent is likely lamenting that Go doesn't enforce compile-time value constraints on enumerated sets like some languages do, but many other languages don't ether. Not even Typescript does. If Typescript doesn't find it important to have such value constraints, Go most certainly never will.
Go doesn't have enumerations. So the first difference would be that my enums would actually exist.
> implicit list structure which, while kind of neat, isn't really a property of enumerations
It absolutely is, or people wouldn't have been regularly writing enums like
typedef Kind enum {
KindSimple,
KindComplex,
KindEmacs,
Kind_NUM_VALUES
};
which they do about half the time.> likely lamenting that Go doesn't enforce compile-time value constraints
Yes, that's my complaint as well. Which is why that "not sure about legality" part in my example: you want to be able to enumerate the enum (duh), but with last_value_of_enum++ being illegal, writing for-loop with "<" is illegal too, that's why there is support for it in for-range.
With incrementing (used in loops almost exclusively) taken care of, the rest of arithmetics on enums is meaningless in general except maybe in case of subtraction (when you use enums as keys/indices for a fixed-sized array) which is why I allow it — but it produces an int, of course.
As for what should happen in Kind(42) example — perhaps it could work like type-assertions?
k, err := Kind(42)
if err != nil {
return err
}That's obviously not true. That's what the iota dance is for.
type Kind int
const (
KindSimple Kind = iota
KindComplex
KindEmacs
Kind_NUM_VALUES
)
Go doesn't have a literal enum keyword, if that's what you mean, but enumerations aren't defined by having a specific keyword or specific syntax. Enumerations are a more general concept, defined as a set of named constants. The above is functionally identical to your example in C, among a host of other languages.> Yes, that's my complaint as well.
Fine, but that's not a property of enumerations. If one wants support for value constraints, surely one should ask for that and not for something the language has had since the beginning?
So that's what I went with, because I actually liked Pascal-style enums but thought they could be somewhat improved, so what you've read is my ideas (Go is surprisingly close to Oberon and both lack enums).
The original comment also hinted at wanting other features that some other languages have, including Pascal, although not stating which features specifically. I expect he was referring to wanting some kind of constraint system, which is the most common feature I hear requested of Go. But that's beyond the scope of enumerations.
P.S generic type sets make it even better. I’ll write an update to my post these days.
I don't see this as negative or "reinventing the wheel". Reinventing the wheel would be writing your own implementation, which doesn't happen if you can choose from many high-quality crates.
There is massive value in this.
The cardinality of the set of "nobody uses anymore" is usually in tens of millions.
I think having a small standard library is actually a good thing because it encourages exploration of the space of possibilities.
For example Go’s stdlib http.HandlerFunc sucks, people instead opt for leaving it behind entirely in favour of Gin or trying to work around it with bad patterns like interface smuggling.
Seems like the C/C++ ecosystem will stay dynamically linked, even with a lot of the industry shifting towards statically linked, fat binaries as disk space is pretty cheap.
I wonder how much simpler Linux packaging would be if everything was statically linked...
The docker world is a neat example of this. The stories of ${absurdly high percent} of containers having vulnerabilities only gets to claim that because of "unpatched libraries." If you reduce it to the number of containers with exploitable vulnerabilities, it is still non-zero, but not nearly as high.
You are, of course, right that it has the possibility of happening. But by and large, it doesn't happen there any more often than it does in the dynamically linked cases. And, by and large, the dynamically linked cases are often a lot more complicated to deploy. (Granted, deployment for either should be doable nowadays.)
The dream, of course, is you just patch the library and call it a day. The reality seems to usually be a circus check on every application that you have deployed to see if they are impacted anyway.
I can be confident I will be able to build >90% of go programs by cloning the repo and calling go build.
If everything is statically linked, you need to wait for every maintainer of every single program on your system to rebuild and push an update. You're basically guaranteed that there is always _something_ missing important patches
And "involve" might mean: wait for a bunch of unresponsive external parties to produce a new binary each for a security issue they might not care about. Of course on very different timelines, etc.
Not only that it can also cross compile for different architecture and operating systems.
Just do `release --name "mycoolprogram" --version "0.1.0"`
and it will output all of your labeled release binaries for every platform your code supports.
check it out! [0] You can see it at work here for this simple markdown blog generator I made, which sports 40 different platform combos [1]
[0] - https://github.com/donuts-are-good/release.sh
[1] - https://github.com/donuts-are-good/bearclaw/releases/latest
On the other hand, if you want to cross-compile a C program using GCC, you need to separately build and install a complete gcc+binutils toolchain for every individual arch that you want to target. And you have to handle the dependency management yourself, which may be tricky if your dependencies' build scripts weren't designed with cross-compilation in mind.
Java is an example -- you'll want your your system java deployment to match the stuff stashed in your jar.
Erlang is compiled but usually releases bundle the VM up with the project, so you typically deliver a full directory with the VM packed in there.
I run a few Go apps, all are single executables, upgrading to new releases has never been easier.
if you have a network oriented application, nothing beats golang as far as release|maintenance is concerned.
Unless you cant use musl for some reason, but that’s a glibc problem, not Go
For instance wazero which I'm really excited about.
https://tetrate.io/blog/introducing-wazero-from-tetrate/?utm...
And there's also "purego" as an implementation that directly generates shellcode.
Maybe those will help you, too?
I am just mentioning these because for my use cases those approaches worked perfectly, CGO free.
Whether a program can be compiled statically is a factor in whether I choose to use it. That effectively screens out many large programs that have numerous dependencies because compiling them statically often becomes too much hassle and the binaries are too large. For example, I compiled dnsdist statically using musl. Of course, "large" is subjective. I consider an 11M C program to be large but some HN commenters using memory-safe languages might think 11M is nothing.
1. Some have dependencies other than libc.
If "you" means me, then no, because I do not have the patience nor do I think it would be worth the trouble.
The binary would be enormous.
One thing I used to do many years ago is create compressed images of directories containing programs or files needed by those programs, e.g., /usr/pkg/bin, etc., using makefs and vndcompress, then save these to bootable USB stick. For example, I might install Firefox, then create the images. The way I use NetBSD is that I boot into RAM from USB. I may or may not use a disk. Writeable directories are mounted as tmpfs. I use RAM as disk. This is one reason I like small programs. Installing Firefox would take up too much space, i.e., RAM, so I would mount the USB stick after boot, then mount the compressed images over certain directories, e.g., /usr/pkg/bin. As I recall, this worked, but eventually I gave up on graphical browsers, as well as X11.
I've found many times that a go binary, even if it has no cgo or cgo dependencies, will randomly require glibc on the target system to execute if you don't explicitly disable cgo in your build.
This used to be easy with C (on BSD & Linux) a long time ago, but then everything started to depend on various shared libs, who then depend on other libs, then things started to even dlopen libs behind your back so they didn't even show up in ldd, etc. Sigh.