Go runtime: 4 years later(go.dev) |
Go runtime: 4 years later(go.dev) |
I appreciate it's error handling. It's burdensome, sure, but it presents almost no additional cognitive load when attempting to reason about control flow.
It essentially has no enums. However, it has a comfortable type system that can wrap primitive types, and you can define methods on those wrapped types. It's serviceable, but not elegant.
I have never been a fan of pattern matching outside of functional languages. The phenomenon I notice in languages that do have it, is the majority of uses cases seem to be a pattern match with two outcomes, one a Some() and the other a None(). It really seems like a more annoying way to write if {} else {}.
It has for, but it also has 'range.' It makes up for a lot.
It has nil pointers, which are just a "zero value." It's not uncommon to make it useful and safer in certain contexts. You can request a lookup in a map that is nil. You can ask for the length of a slice that is nil. You get sensible answers still. There are still footguns, but they're all single barrel.. which is _nicer_.
I don't need a perfect language. Good enough with low cognitive load and amazing cross-compile facilities I'll take any day of the week.
EDIT: In fairness, there is one thing and exactly one thing I miss about Go: compile times. Rust is simply no competition here, but in trade, it is doing a ton of extra work. Trade offs.
I wished Go formatter would compress
if err {
return err
}
to if err { return err }
Even better would be to extend the syntax to allow if err return err
But I am Ok with braces.There are two reasons to have match: exhaustiveness and destructuring/pattern matching. I don't think there's an if/else parallel to `match result { Ok(t) => { ... }, Err(e) => { ... } }`
The fact that a `nil` value can be made useful (which, AFAICT, means it won't blow up your program?) is not a good enough reason to include them in the language. Rust can do perfectly useful things with, say, `Option<Vec<T>>`, and it's possible to distinguish between `Some(vec![])` and `None`. If you care to ignore that distinction you're free to do `maybe_vec.map(|v| v.len()).unwrap_or_default()`. But IMO it's much better to tell the compiler to unify different cases (`None` and `Some(empty)`) than to have to work around the compiler's unification of those cases when you don't want it. It's always easier to increase entropy than to decrease it. (P.S.: should the sum of a nil list be 0, or NaN, or...?)
In Rust you write this type of thing as:
if let Some(name) = order.get_name() {
println!("Order ready for {name}!");
} else {
println!("Order number {} ready!", order.num());
}
So, it's a destructuring pattern match just written as an if-else. Because we did the match here, we can't forget and end up using name when there wasn't one, the variable only exists when it's bound.I also personally like Go a lot. It's filling the gap between C++ and Python for me. If I need something compiled with proper threading support, but C++ would be an overkill, I reach for Go.
Go is designed with a human centric view, IMHO: "Make writing great programs easier rather than design a language with novel/cutting edge features, but with a high cognitive load", and I find it as a noble aim as Rust's guarantees and aspirations.
I understand why people love Rust, but I don't think it's the only one, or the proverbial silver bullet that we finally achieved. Yes, it's nice. Yes, it brings many things to the table, but it's not the final solution for once and for all.
You like Rust? Great, Go for it (unintended pun alert), but I think we need all languages from all paradigms and perspectives.
I find this talk [0] very informative and useful while interacting with other languages and communities. I'm not sharing this with any insinuations towards you, but just find it generally perspective broadening.
[0]: https://www.youtube.com/watch?v=YX3iRjKj7C0 - What Killed Smalltalk could Kill Ruby.
If it were Go's design philosophy, it would have allowed unused variables/imports. Those restrictions are there exactly because they help computers, reducing compilation time. The over-focus of compilation time also stems from monorepos being used by Google, whose purpose is also helping computers.
I don’t understand his horrid informal writing style trend. You are clearly not quoting anyone and just providing your own interpretation. So why in the hell are you using quotation marks? In this case you could just ditch the quotation marks altogether since the colon already acts as a separator.
First it would need to add error handling before it could look to improve upon it.
I'm not entirely convinced it should. I spend my days in a variety of other languages that have added error handling in various ways and, in my experience, it always ends up making errors unnecessarily difficult to do deal with. I regularly wish the idioms of those languages recognized errors as being core to your application as any other value, not something to treat differently. Go really got things right for the type of software I write.
But not all software is solving the same problems. There are a lot of programs where you don't need to think about errors; where stopping the world is fine if you encounter one. Go is not at all a good fit for these situations. However, I think it is okay for Go to not try to be all things to all people. We already have plenty of other good languages that serve other niches. Right tool for the job and all that.
varFoo, err := GetFoo()
if err != nil {
return err
}Can be written as:
varFoo := GetFoo()?
Just like Rust, everyone would stop complaining about Go error handling. But they have this absolutist position on syntactic sugar, even for something like this that would make the language that much nicer to look at and work with.
What kind of utopian programming job do you have that you don't have to think about errors? An error is not limited to technical issues like packet loss or unable to open socket. Its also "client A attempted to purchase item B which is limited to client C". How do you express this in Go?
I have no idea why people are so opposed to ADTs. Its like sliced bread with butter, or whatever the phrase is. Its not _that_ complicated...is it?
I'm not sure what the better way to do it is tbh.
Honestly, I suspect the deficiency is on my end. Perhaps I’ve just spent too much time with Lisp and have experienced some professional deformation.
I think something way more radical than "C++ threads but with a built-in concurrent queue" is needed. If the default was to share nothing, and any shared memory had to be explicitly shared somehow, that would be a great step in the right direction. Maybe the compiler could even check that explicitly-shared variables are protected by a mutex; something like how Rust mutexes "own" the things they're protecting and you can't access the underlying object without having the lock.
I love writing lisp, but I hate reading & debugging lisp.
OCaml kind of gives you that, but the dev experience isn't as good as Rust or Go in my opinion. Still, I enjoy it a lot, more than Go or Rust.
"More expressive" in my opinion tends to mean "less readable", but there is some personal bias here. The people I've met during my career who like "more stuff" are usually the younger, less experienced programmers who aren't as concerned about long term viability.
I'm in my fifth decade of programming and my approach is "do more with less". When people want more stuff, for me, it means that I'll be dealing with more cases of people not knowing how all the stuff works. Everyone likes to think they can deal with more cognitive overhead, but mostly people can't.
I'm not sure if pattern matching belongs in a language like Go at all. Admittedly I haven't given it much thought, but it doesn't feel right for what Go aims to be. If you want to do Erlang, do Erlang, but I might be wrong about this.
What's error prone about the for statement? I don't use the C-style for statement all that often as way more than 50% of my for loops are ranging or naked for loops with break/continue/return.
Not having nil tends to lead to having to invent sentinel values all over the place, doesn't it? Is that really better? And since you often end up doing things by value in Go anyway, it isn't like you don't have any choice.
To get into it, it might help to start by modifying an existing project, a lot less painful than trying to write something from scratch without being familiar with the idioms.
That's fine, we all have our preferences. I love Python, but I can't stand Ruby. I love Go, but hate C++. I make my living with C#, but won't touch Java.
It's okay. We're not wrong. We're just different.
In my opinion: Go needs a Kotlin. First and foremost to do away with implicit nulls (imho the biggest mistake), but here are other things that could be impoved you've already mentioned.
> has error prone C-style 'for'
Not really true. It has `for index, elt := range v` to iterate over arrays and maps. It's a pity you can't define your own range, that's true.
My issue for application and web server development are in the language design as it restricts me in my personal quest to write loosely coupled code that is marinated in unit tests - at least when compared to other languages.
That said I enjoy the language, have learned a lot from it, use Go often (previous day job) and have a gopher plushy - but I am just not a die hard loyalist. There are things about the language that blow others out of the water, but there are significant portions that leave a lot to be desired.
What I have noticed is the language design has inconsistencies and there are lots of exceptions built into the language to get around missing features; features which are sometimes filled in later leaving ambiguity around approaches.
For example, Go uses a nominal type system with the famous exception of interfaces with methods that are structurally evaluated. The issue is interfaces only feature structural type evaluation on the top level, anything nested beyond that is nominal.
So an interface that has a method which returns an interface requires an implementing struct to return the _exact_ interface from that same method.
You can accept a struct where an interface is a parameter but you cannot return a struct where an interface is the return type.
This is useful when building type safe dependency containers, something that makes unit testing much easier.
Instead people just overuse `Context`, prop drill it into everything and cast types at some point when trying to get things from it.
Other examples are the return types from functions. Go didn't have generics initially so a Result[T] wasn't possible - but also exceptions were not possible. Tuples were not allowed by the type system but the language makes an exception for the function return types - fair enough.
Without type parameters and a need for generic basic types like `map`, `slice` and `sync.Map`, the language made an exception for the the primitive types - giving them generics however this is now inconsistent with the current implementation of generics.
Usage of `make()` and the confusion brought on by managing "references" also adds a bit of friction to the language.
Generics are inconsistent as well - for instance you can have a struct with a type parameter but not methods - however you can have functions with type parameters that accept a struct as their first parameter.
The tooling is a little underpowered, the test coverage analysis tool doesn't consider branch coverage, only statement coverage so you can have 100% test coverage reported with only 50% truly covered.
Making mocks requires cumbersome type generation - this is something I am okay with but the types generated have terrible assertion capabilities.
But there are phenomenal things from the language. Packages are a great design choice, module management from git is simple and effective, `range` is chefs kiss, the compiler is beautiful.
I love the idea of goroutines but I'm not blown away by channels. They are fancy iterators with dedicated keywords which I find can clutter things up a little - but they were really cool when Go first came out and we were only just starting to think about how to manage asynchronous concurrency.
Possibly I'm misunderstanding the complaint, but that is because an interface is a fat pointer, so one returns a pointer to a struct implementing the interface.
If I had to make a bet, I’d say Typescript will take over for everything not performance critical and Rust will fit in every spot that either requires high quality assurance or high performance.
> That said, I don't know what I'd want to drop from Rust so maybe I'm just fantasizing.
What is X? I don’t know.
* No optional/named parameters. Writing a whole function per parameter for function chaining is excessive. This would not be difficult to add to the compiler (i've done it and have seriously considered using the fork) but it seems like the team is just stubborn about adding stuff like this.
* No default struct values. 0 isn't good enough in real world scenarios.. have fun writing BlahInit(...) for everything.
* Would be nice to get the question mark syntax for error handling. Error handling shorthand for if err != nil would also be very welcome.
I love Go too but this does drive me nuts, especially when parsing JSON and wanting to set sane defaults for missing values. Like, for example, booleans that should default to "true".
The unmarshaller will iterate on the json input and set struct fields when json fields are found. This means that struct fields that don't match the json are ignored, and values you have set before will be left as is.
Pike slapped his Newsqueak's CSP paradigm on top and the rest is history.
How to combine sum types and a precise garbage collector?
https://developers.redhat.com/articles/2022/04/19/java-17-wh...
I think I'd prefer 2. But 1 will always remain an option I suppose.
And on the other hand, it's a simple flag. They added one memory flag in over a decade. I think they're holding the line on complexity pretty well?
1. runtime feature to limit memory use
2. a mechanism to configure it
What we just don't have yet is
3. standard way to read container memory limit
Once that comes to existence, the first two were gonna be needed anyway. And now you can experiment with #3 for the various container runtimes.
Besides its GC implementation that works very well most of the time, it also has simple but strong debugging tools to investigate how well you are handling memory allocation and CPU usage. Enforcing consistent coding style also makes it very easy to read other people's code and quickly contribute.
My suggestion to others is to avoid prematurely optimizing memory usage (e.g. trying to do zero-copy implementations) and always investigate performance via these profiling tools Go offers (e.g. pprof). Very often Golang's compiler or runtime will automatically optimize code that may not seem to allocate optimally.
There are still downsides but some of them are actively worked on:
1. Generics are still lack-luster (... but good enough to start replacing a lot of boilerplate code and will improve later on)
2. Error handling is still tedious (fortunately it seems to be their next big focus), it should take less code-space and have an option of stack-trace
3. Stdlib logging is too simple, lacks levels and structured-logging-style (currently actively discussed: https://github.com/golang/go/discussions/54763)
4. Low-level UDP networking is weak (soon to be fixed as they accepted this proposal: https://github.com/golang/go/issues/45886 ), this will become more important as we transition to QUIC protocol
5. CGo (C interop / C FFI) is pretty bad performance-wise compared to other languages, it's OK if it is an I/O operation but anything C interop that requires low latency won't be running great
6. Similar to other languages, nowadays there are growing external dependency trees (e.g. I always wanted to use testcontainers-go to make integration tests easier but man, have you seen the dependency tree you will pull in with that pacakge?), solution in my eyes is flattening and standardizing most commonly used packages but Go in particular is very opinionated regarding what is included in stdlib
The above downsides mostly come from the background of creating distributed data pipelines and certain data collectors/parsers. For building API servers, I keep hearing it is a godsend in its simplicity, so your mileage may vary depending on the business domain you work in.
I would be interested to hear other people's experience with Go, however!
it makes great sense for network with concurrency, not so with real time or low resource devices to me.
I thought JacaScript doesn’t have destructors (in the memory/resource management sense) or finalizers…
I don't think that's true, otherwise it would've been fixed already. For the main use case of Go (https://go.dev/blog/survey2022-q2-results), APIs and web services it just doesn't matter if the binary is 1MB or 30MB. Unless you are working on some embedded systems where space is scarce I don't see it as a big issue.
- Pull the prod image to my laptop in ~1 sec vs. pull the image in 10ish seconds
- CI build and push the image instantly vs. ~2-3+ seconds
- Long-term (1y+) retention of per-pipeline/push artifacts vs. per-branch/tag artifacts.
- All images in each node's cache vs. 50% of images in each node's cache.
Yeah, I don't really give a shit about the 30MB once it's out there. But there's also all the steps to get it / keep it out there.
You can imagine transfer costs in a CDN, storage, etc all mattering in the edge cases. Not everyone would have this problem but if your Go app is sufficiently popular and you need to distribute it you may see this as a problem.
See Terraform[0], Traefik[1], or OctoSQL[2].
Though I agree plugins would be welcome, especially for performance reasons, though also to be able to compile and load go code into a running go process (JIT-ish).
[0]: https://github.com/hashicorp/terraform
[1]: https://github.com/traefik/traefik
[2]: https://github.com/cube2222/octosql
Disclaimer: author of OctoSQL
If you have a language that has a module ecosystem, compiles fast, and generates a single binary, take advantage of that; don't try to reimplement fragile dynamic linking.
One way I've done it in the past is via sub processes talking via stdin/stdout. Another way I've done it is just via a regular REST API.
You'll never get enough performance for a lot of tasks if you need to pass a lot of data between applications. But depending on your needs it's more than serviceable. The benefit of this is that plugins don't need to be written in Go, as you have a simple interface.
A good example of this is something like LSP. LSP plugins are abundant and each one is written in a different language. VSCode is Typescript, the Go language server is written in Go. Both can speak to eachother fast enough.
But I'm ignorant: is the Go runtime built in any way to accomodate third party languages? Apart from writing an interepreter in Go, is there a good way to target it?
Something else is that the semantics of the Go language itself are purposely limited in ways that help build an efficient runtime easily. So even if you could target the Go runtime easily, it would impose limits to what the language can do as I understand.
Edit: https://github.com/minio/c2goasm seems to slightly go in that direction.
On similar lines, Fable [0] project recently announced rust & dart runtime support making F# a very attractive choice.
[0] https://fable.io/blog/2022/2022-06-06-Snake_Island_alpha.htm...
https://www.ptc.com/en/products/developer-tools/perc
https://www.aicas.com/wp/products-services/jamaicavm/
And while we are at it, for .NET as well,
But if you look at e.g. Kotlin, or C# with "#nullable enable", it tracks whether a given reference can be null or not. So you write if-else code instead of match, but you have to do that in order to actually do something with a reference.
"Nil is useful", in Go, means that for expensive objects it's often appropriate to pass nil instead of an empty or defaulted object, and call methods on it that act as if it was an empty object or with some default/no-op behavior. This is most obvious with lists/maps but can be applied to all concrete types.
While I can imagine a language in which `Option<T>` can re-export some of `T`'s method set with a default behavior, in practice I don't know any which actually do this.
And to be clear they exist, but you have to compile them with the same go version and if any dependency is shared, they need to be exactly the same version. There are also serious limitations in the sharable code
Most of the time this approach is not an issue, and it works really well. But you need to be aware that it's best to keep the number of providers in the 0-20 range per root module.
The other is most UIs are wrappers around other functionality, often in libraries. Working on anything other than pure Go code bases adds additional friction, and dynamic libraries add to that complication considerably.
I believe Go could be a really great fit for UIs, but likely never will be as there is little economic incentive to put in the incredible heavy lifting required to build up the support libraries. There are certainly some small projects trying, but without massive resources backing it, it is a struggle to achieve the full fit and finish that we've come to expect.
My job primarily requires thinking about errors. It is why I wish for Go-style errors in the languages I use.
But on rare occasions I write things like batch scripts, automations, etc. in which failure means addressing the issue in realtime and trying again. There you don't care much about errors other than ensuring that the world stops when an error occurs to allow you to fix the problem before continuing.
While contrived, if you run into a "client A attempted to purchase item B which is limited to client C" error in this space you're probably going to have to phone them up and tell them that you can't process the transaction, apologize for the mistake, remove record of their purchase, and then once complete run the script again. The program crashing with an error message is sufficient here.
Different tools for different jobs.
https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
It does have some unique ecosystem elements such as Coq, WhyML, Mirage, etc. But common components could be definitely improved.
It's because an interface is structurally evaluated only on variable assignment, but it's not always structurally evaluated which limits its usefulness
Watch, learn, download, and enjoy this V program (https://youtu.be/6H7dprSwr74), among so many other examples (https://github.com/vlang/V/tree/master/examples).
And, its not necessarily an "either or" or "us versus them" situation, people can use both or whatever else "floats their boat". It's a bit disheartening to see programmers that fall into being so closed-minded or disparaging to other options or something new.
Kerbal Space Program (videogame) is a big compiled executable and plugins are loaded as DLLs from a directory.
I'm not trying to reimpostare anything, I just wish it was available.
Defer helps but some true lexical scoping might be nicer.
There's a Go code base that a friend and I first wrote 5 years ago and we incrementally update it as we find small bugs because of a system it interacts with that changes. We usually touch it once every quarter. I've had no trouble re-reading the code base and keeping it up-to-date. I can't say the same for a lot of other code bases I've worked in.
WebSocket's destructor: https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/c...
HTTP server's destructor: https://nodejs.org/api/http.html#serverclosecallback
Etc etc etc.
You're right that JS/C/Go don't have language features called destructors, but they have a whole bunch of types with ad-hoc destructor functions which you have to remember to call if you want to avoid leaking resources.
Otherwise you’re just saying “I can’t write Go like PHP” - no shit, that’s a good thing.
Perhaps the Rust burrow-checker will prove to be the second silver bullet for bug reduction, but for now, the only thing that has ever been invented in programming language design that provably reduces the amount of bugs in an application is having a GC (not types, not getting rid of null values, not monads or other HKTs, not CSP).
But, I'm not sure that is even the primary benefit. The primary benefit is that you won't be incentivized to leave unused imports and variables to litter your code, as I see happen all the time in languages that aren't so strict. The Go team has even stated that they decided there would be no warnings because they've learned that warnings get ignored.
If something can be abused, it’s going to be abused.
Pollute implies that it is unwanted, but this is something you do want. It is the most interesting and important part of your application logic. You want it up front and centre for all to see.
I think we all understand the human desire to want to believe that bad things won't happen, but when one becomes an engineer they have to set those emotions aside and realize that bad things will happen and that your job is to make sure that when bad things do happen that the failsafes achieve an acceptable outcome.
> I'm not sure what the better way to do it is tbh.
I'm not sure any better is fundamentally possible within an engineering context. The vast majority of the job is in understanding the failure modes and being able to communicate to the computer how to gracefully deal with the problems when they occur. As such, it stands to reason that the vast majority of the code is going to be related to errors.
If you are programming for hobby/learning purposes, where when bad things happen your program can simply crash with no consequence, there are different approaches that work well, but Go is decidedly not designed for this space. And, frankly, doesn't need to be as there are already plenty of languages designed for that. Go is, quite explicitly, meant to be an engineering language.
And the worst part is when reading the code in such a situation, it can get very tricky to figure out exactly where the exception is thrown.
There's a reason that the JVM ecosystem has largely moved away from checked exceptions.
We got rid of the getter setter silliness to be in “constructor land.” ( immutable records from Java 17 )
Function are still not first class citizens, but … they have a working visa now. You can shove lambda anywhere.
It’s still awkward to write functional code
Meh. Recently I’ve been really productive with it and I like the sheer boredom and lack of surprises.
Compiles to native code, has a repl, version 5 is multicore for those not happy with Lwt or multiprocessing in the UNIX classical style of each tool does one thing, and a GC only second to GHC in handling immutable types.
I'm excited for version 5, but until it's out, it's hard for people to take seriously in conversations about _Go_ of all things.
Maybe they should spend more attention in their compiler design classes regarding runtime implementations and language semantics.
Not sure about any objective metric on the other languages, though. Tried to look at ANTLR grammar files for each, but grammar is only one part -- language semantics are not included. (E.g. Rust's grammar is slightly smaller than Java's, yet I don't think many would argue that it is the easier one). JavaScript, while easy on the surface, can actually be quite complex/has many non-idiomatic concepts, e.g. `this` handling, property flags, etc. Python has similar "rabbit holes".
Writing and reading simple Go libraries is a breeze. I’ve written tiny libraries that fit in one file. You can read it linearly, starting at the top and following the logical progression of type declarations, functions, globals, etc.
Whereas with an identical Java library, a reader would be presented with several small files and have no idea where to start reading.
Perhaps you meant statically compiled binaries? Even then I'm not so sure about that. Right now I'm working on a project that statically compiles a plugin so its various libraries can't leak or be overridden by the loading application, yet which itself can load other plugins. OTOH, this is in C, C++, Objective-C, and Lua (basically C from the perspective of binary linking), all of which have mature linking semantics. (Well, at least this is true for C. Controlling symbol namespace pollution by C++ and Objective-C code requires more complexity than for C, but at least the necessary compiler and linker flags exist and are mature.)
When you use languages or toolchains that don't invest in an ABI, or which make linking too automagic (with no or little recourse for exposing various linking features a platform may offer), then runtime linking is likely to become a major limitation in some solution areas. For example, there's probably no way to coax XCode to compile my plugins properly, without outsourcing some logic to additional scripts. Ultimately a Makefile is likely the best solution--and the one I chose, anticipating these headaches--as it keeps most of the build transparent. Fortunately, XCode seems to just be a wrapper around a bunch of command-line utilities, so you don't actually need XCode at all.
The annoying part about the scripting language is that if something is written in Go, you want developers to also work in Go, otherwise the people writing plugins for your software, knowing better the API and use-cases, have a disconnection with the language actually used for the software, losing potential opportunities for contributions.
It's also why it can do things like generic virtual methods - it can compile an instantiation and adjust the vtable at runtime.
It is possible that with other error-related features added to the language you could avoid those traps, but Go doesn't feature those either, so simply adding that construct without thinking about the problem much more deeply doesn't buy you much.
If you are solving a stop the world when you encounter an error-type problem that might be okay, although I'd argue that you may as well panic instead. But, again, Go isn't designed for those problems and I'm not sure it needs to be. There are already plenty of good languages designed for that type of work.
The reasonable thing is perhaps to log it there at most, and bubble it up (possibly wrapped as you mentioned). It can be handled for example by a request handler, by returning a 50_ error.
But other layers of abstraction lose their utility value if they start to make assumptions about the caller. Maybe in your web service a 50x stop the world error is all you'll ever need if there is a database error, but the next guy using the code for another purpose could have very different requirements and when you have to actually deal with errors, exceptions become a royal pain very quickly. As such, the official line is simply that you shouldn't let errors casted to exceptions cross package boundaries.
But, again, Go isn't really designed for stop the world programming and it's okay to use another tool if your problem space is suited to stopping the world.
The nature and culture of Go makes it harder to change direction and be as amenable, but the merits of such conservatism is a "depends" type of thing and can mean incorporating things many years after it has become fashionable or accepted.
I'm used to a question mark being around null handling, but you know, JVM languages lol, null is thought about a lot.
Go is an experiment in the exactly opposite direction: language puts pressure to really handle the error in a meaningful way every time. And if you bubble up, at least describe the situation a little bit better. Or explicitly give up and receive a penalty for that: the `return err` is nothing to be proud of, it's just a visual penalty for _lack_ of error handling.
I'm not saying it's better in every project, I'm saying it's valuable on some projects.
OpenMP uses OS threads, doesn't it?
When you have coroutines that can be preempted, without explicit yielding in the source code, that's the line at which I would consider it a VM. Basically, it's a VM if some userspace code (JIT, GC, scheduler etc) runs in the background and does things to your code.
I don't think there's a definitive interpretation of VM, so this is all arguable. But e.g. Java and C# are generally considered VM languages even if AOT-compiled, despite the fact that there's no bytecode involved past that point.
And why the distinction between OS threads and green threads? Sure, in case of OS threads, it's not the language runtime that does the scheduling, but that just means that the operating system is now our virtual machine.
I would argue that what makes a language runtime a virtual machine is not the presence of a garbage collector, a jit compiler, or a scheduler (with or without preemption), but that it has well-defined semantics in terms of something that looks a bit like a real machine - hence the name! In particular, there's a set of instructions it understands. In case of the JVM, the instruction set is defined in the Java Virtual Machine Specification (with Java bytecode its representation), in case of the CLR, it's defined in the Common Language Infrastructure specification (with CIL bytecode its representation), in case of Smalltalk, it's defined in the Blue Book, in case of WASM, in the WebAssembly core specification.
So, why in the hell are you just berating me via a comment box?
I made a mistake, alright, and used quotation marks as a tone modifier, because I know no other way to do that.
However in this case you could just pointed me the right direction without berating, since pointing out the mistake already acts as a kind direction arrow.
Did I even hint that I was assuming anything about native vs. non-native English speaker? Or blaming non-native speakers? No. Play that wounded ESL violin somewhere else.
> I made a mistake, alright, ...
I said that I think that it is bad style. Alright. I didn’t say that it is wrong. (People are doing it a lot these days so apparently it isn’t wrong.)
> Play that wounded ESL violin somewhere else.
I'm not playing a wounded violin, or any violin for that matter. I'm a double bassist. Jokes aside, I'm not being apologetic, just explaining my position. On the other hand, if reading a comment squarely in your own tone bothers you, you might want to think on that, at least a little.
Your wording of your comment implies that I'm doing something wrong by using quotation marks, and I take a note for that. Why are you so upset because somebody admits that there's a room for improvement?
The only thing I'm not agreeing on is your way of sending the message to this shore, that's it.
The thing is, neither English punctuation, nor Hacker News has many facilities to convey tone while discussing. I like to do that in my writing style to convey feelings and tone of my comment. Quotation marks seemed like a usable way to do that, and I possibly got used to that from literary works I've read, so I copied the method.
However, if a native (or more knowledgeable) person tells me that I'm wrong, I tend to believe them and try to learn the proper way, that's it.
However, (stable) OCaml not having multithreading support is still a gigantic limitation. Also, OCaml supports fewer target platforms, and I believe it's worse at cross-compiling (though I admit I may be wrong on this).
OCaml 5 still seems a lot more real and coming-sooner than the imaginary OCaml-on-Go. Sure, it's not ideal that multicore OCaml isn't yet stable, but it does exist, and will become stable.
Yes, multithreading support is only in version 5.0, yet for many decades UNIX was multi-process only, and thanks to the latest security exploits, sandboxing with multi-processing alongside IPC seems to be the latest fashion anyway, so no big deal.
OCaml has several backends, including a bytecode one that is used for the REPL and porting purposes.
For example, if you want a high-performance web server, it's much more efficient to serve requests through multiple threads (preferably also using efficient IO) than it is to spawn different processes for different requests. I imagine you can coordinate different processes in similar ways, but again, it's much more effort, and either way, those processes are far too intimately tied to each other at that point to call them "different tools".
public class Wrapper {
public static class C1 { }
public static class C2 { }
}
//other module:
var x = new Wrapper.C1();The Go style just lets your code live together for easy reading, no problem. You can comfortably fit all of this into one file: a one-function interface, a couple of small functions that take the interface as a parameter, and two implementations of the interface.
Compare this to 3 or 4 tiny Java files. You’d have to guess which one to click on first.
I wish https://pkg.go.dev/plugin was clearly documented as not being a viable 3rd party plugin solution.
(There are also higher-level ABIs like COM, but they are usually reducible to the C ABI in practice - e.g. COM can be described entirely in terms of struct and function pointers.)
I have written both languages professionally in various contexts for years. In point of fact, I've been writing Rust since before Rust 1.0 came out, although I didn't get my first Rust job until about 3 years later. They're both excellent languages. I actually didn't learn Go until several years after learning Rust, and at the time I didn't appreciate it like I should have, since I was so excited about Rust. Only after getting professional experience with Rust did I truly begin to appreciate how nice Go is for the things it is good at.
Go is just so much better suited for network-connected services (including but not limited to web backends), and Rust is better suited for batch processing or embedded contexts where no compromise on performance can be made, in my experience. There are lots of reasons for this, but it's off topic from the question, and the answer to the question is that I absolutely do go back to Go. Neither language is a perfect tool for all problems, and I constantly dream about building my own language that learns from the best of both... there is certainly room for improvement on both ends.
You can compare its domain (to a certain degree) to Java or C#, in which case the latter two are superior due to the reasons mentioned previously (enums, pattern matching, etc.).
If you have never used C++ and your baseline is Go or JavaScript then it probably looks like a confusing hellscape. You have to learn a whole new kind of type system, a new nomenclature (“Vec”s instead of “Array”s), new concepts like allocators, plus the borrow checker and lifetime annotations, and module/crate system. Former C++ programmers only have to learn the latter half.
That’s because Go does not have async code. It has sync code on an async runtime. Like e.g. Erlang/Elixir.
This is subjective - I for example have no real issues reading Rust code, but find Go to look like utter spaghetti on the screen.
Rust requires designing the memory management (ownership/lifetimes) of a program, which Golang doesn't, so even if/when the cognitive load is (hypothetically) equal, there's an additional, non-trivial demand. This is not something that everybody wants in a project (the given tradeoff may not be worth); for sure, if I had to introduce a language in my team only for tooling purposes (ie. small programs), Rust would not be the most productive choice IMO.
But it's not easy to quickly prototype stuff with. Just recently I had to write no less than 7 small prototypes and I gave up on the second one, relearned Golang -- took me an hour -- and finished 3 prototypes in a day.
I tend to go all the way in languages so I can use them freely afterwards. But... In Rust's case I just can't justify the effort. Golang really helps you start off a project faster.
I'll still 100% Rust. I'm working on it every day. But indeed, let's use languages where they are at their best.
I have had year+ long breaks between writing Rust applications. I have been able to get back into it without issue very quickly. At least one of those times I successfully completed a large, sweeping refactoring of a project of mine that had lay untouched for three years. I can confidently say it would have been extremely difficult to do that same task in other languages I consider myself very proficient in and use daily.
This is basically the "Anyone who doesn't love my favorite movie hasn't watched it enough times" argument. Programming language design is a complex space, there are no "correct" opinions.
I don't want to take sides, I want to enjoy programming and learning, that's all. When zealots zeal, kinder people either leave or go underground.
No part of my comment was doing this, and this bit is entirely unnecessary and adds nothing to the comment you wrote except fan religious wars about languages.
I think my comfort with Rust moreso comes from it feeling like any other C-ish language in appearance. It reads as expected to me.
Obviously you didn't mean that as a universal rule, but for posterity, I'm a counter-example to that. I learned Rust before learning C++ or Go. If anything, I think this biased me _more_ towards Rust than the others; C++ just kind of felt like a more error-prone, less ergonomic Rust, and Go just felt like it took far too much boilerplate to get anything done.
I'm not sure this is accurate - have you seen modern TypeScript? I actually find that more confusing than Rust to read.
(FWIW, I was predominantly Python/JS-centric before just going "all in" on Rust. It's not that bad, in my experience - if anything, I find Rust "just works" whereas I got tired of the churn in those environments)
Rust and C++ are like Hebrew and Arabic. Difficult languages for an outsider, but if you speak one you have a significant head start towards learning the other. TypeScript is Japanese: knowing Japanese doesn’t help you speak Arabic, despite both being difficult.
On the other hand Golang can be fun in like a week of learning it.
Meanwhile Go doesn't even have algebraic data types. I can't imagine working with a language that doesn't have these kinds of functional features anymore after having gotten used to them.
But sure, one is a low level language which can and thus must care about every little detail, while the other is a managed language. (And honestly, mixing the two as if they share the same niche is very off putting)
I you were to learn Rust from scratch, what project would you recommend for the "changing and breaking" approach?
But along the whole "break it" idea... I don't know if you're an IDE person or a text editor die-hard, but I've found that rust-analyzer helps a ton. I'm historically a "vim with no plugins" kind of guy, but I'm using VS: Code with the vim keybindings now, and even if I'm not changing some code and getting feedback from the compiler itself, using rust-analyzer to go "hey what's this type here? Where's it defined, let's go take a look" has helped a ton.
That said, types help and poking around helps, but it's not always a panacea. Today I'm working on fixing something that doesn't quite work, even though yesterday I figured out how to assemble everything I'm supposed to need from this library I'm using. "It compiles it works" is a thing people say, and while I feel that way often, it's not true all of the time, of course.