The article has a technical inaccuracy with respect to Go’s path printing. It claims that Go prints the “wrong path”, but it prints exactly the bytes in the file name. This can be confirmed with strace -xx:
It’s unclear what they mean by “wrong” but I guess they don’t want to it to be so easy to write non-utf-8 encoded bytes to standard out?
I also disagree with the characterization of Go saying “don’t worry about encodings! things are probably utf-8”. There’s exactly two spots in the entire language that tie strings with utf-8, and that’s when you range over a string or do conversions with []rune. In every other spot, they are byte sequences.
This post makes some valid points, but it’s complaining about things that apply to almost all major languages.
Like, the first third of the post is simply saying that platform-agnostic stuff assumes a unix-like interface, and then simulates it if it doesn’t exist. Which is… exactly what c does, and exactly what python does, and exactly what java does, and exactly what nodejs does, and exactly what tcl does. You can theoretically design a totally new synthetic interface that exposes all the features somebody might possibly want & then simulate it on all other platforms, but then you’ve alienated everybody who has ever used a different programming language. Or you can do some kind of windows-first thing & simulate windows behavior on non-windows systems, but even stuff developed at microsoft doesn’t tend to do that.
The last third is complaining that go packages can have arbitrarily large dependency trees. This is true, and it’s a problem. It’s also endemic across every language with its own package management ecosystem, & it’s hard to find packages that don’t pull in the whole universe when dependencies are automatically pulled in. (In languages where automatic dependency management is rare, dependencies tend to stand alone more often, simply because if they required too much nobody would bother to use them. In either case, it’s a matter of package maintainers having varying levels of sloppiness!)
In other words, it’s a little weird for this post to be framed as an exhortation against using go, and not a complaint about the norms of modern languages! Everything it says about go also applies to python and javascript.
The last third is complaining that go packages can have arbitrarily large dependency trees. This is true, and it’s a problem. It’s also endemic across every language with its own package management ecosystem, & it’s hard to find packages that don’t pull in the whole universe when dependencies are automatically pulled in.
While true, I’m pretty sure the OP was making a different point. The OP was using a package that imported github.com/aristanetworks/goarista/monotime located at https://github.com/aristanetworks/goarista/tree/master/monotime, but as a consequence, it also pulled in everything else in that repo, even though monotime doesn’t explicitly depend on any of it. You can try this out yourself:
$ go mod init gobig
$ cat main.go
package main
import (
"fmt"
"github.com/aristanetworks/goarista/monotime"
)
func main() {
fmt.Println(monotime.Now())
}
$ go build
$ less go.sum
Now, when I run go build, it doesn’t actually seem to download/build everything that’s in go.sum. So perhaps the problem isn’t as bad as it appears, but that go.sum file is still pretty scary.
(I’ve also run into annoying problems similarish to this, where it’s impossible to specify that a dependency is only used in tests. But I guess that goes back to the whole “simplicity” question…)
That’s certainly a problem. It’s not Rob Pike’s problem (or the Go development team’s problem) that a popular third party package was sloppy about minimizing its dependencies (although the decision to have automatic dependency management tooling ship with the language makes it more likely for this to happen, as does sheer popularity). Go doesn’t seem to be unusual in this either, though I’m sure the static-build-by-default setting makes it more visible in the form of large binaries.
I think the point here is that the Go package manager makes it very easy to be sloppy in this regard. AFAIK, other language package managers do not have this very specific type of problem.
exactly what c does, and exactly what python does, and exactly what java does, and exactly what nodejs does, and exactly what tcl does
These languages, though, don’t market themselves as “simple” - and at least in the case of C, it’s kind of dubious to say that it “assumes a UNIX-like interface, and then simulates it if it doesn’t exist”, since at its inception there was no such thing as UNIX, and since it is standard on Windows to use file handling libraries that are written, in C or C++, for the Windows API.
Everything it says about go also applies to python and javascript.
The point of this article, at least as I understood it, is that Go’s claims of “simplicity” aren’t well-founded. Python, for example, doesn’t hold simplicity as a core value, either in interface or in implementation. Obviousness, clarity, etc, but not simplicity.
Isn’t the point of the Zen of Python being written the way it is to invite discussion of the nuances and interactions and inherent contradictions between these statements?
I believe so, and this is clearly unlike the Go team’s philosophy regarding “simplicity”.
huh, I never really read it that way. I never really found it very Zen-like, the usage of Zen in this context honestly seems … just like weirdly appropriative? They’re not exactly mysteries or parables, they’re just short statements of values.
do you read any of the other statements in the Zen of Python as contradicting “simple is better than complex”? My time programming Python (years ago I was a professional Python programmer but I don’t use Python much any more) definitely made me feel that simplicity was a stated value of the language and the ecosystem, although the follow-through on that value was … somewhat inconsistently applied.
It’s perhaps appropriative, though not really weirdly so; there’s a practice in Zen of recitation and meditation on the meaning of collections of koan, each of which is a small, relatively simple — at first blush —statement generally about a single philosophical truth, but it’s the whole that shows the ebb and flow between each individual statement. They don’t have to be either parables or mysteries, merely a set of things that push and pull on each other, like the wind blowing the bamboo about.
So “simple is better than complex” doesn’t need contradiction, but it is balanced by “complex is better than complicated” and given more flavor by “sparse is better than dense” … in all things lean towards the left of each of the first six statements, but accept that nature of the problem may push you towards the right… something can necessarily be the far right on all six and still be Pythonic, but only if the problem (and not our own vanity) requires it.
So simplicity is a virtue, relative to complexity, but complexity is a virtue relative to complication, and so on.
As appropriations of Zen go it’s not all that weird, and also not that far off the mark.
Sure, PEP 20 is full of contradictions, which I think we are meant to contemplate productively (though I don’t disagree with you that “Zen” is perhaps going a bit far.) Consider even just lines 1 and 2:
Beautiful is better than ugly.
Explicit is better than implicit.
The beautiful interface is often the one that hides some of the ugliness inherent in the domain; the explicit interface is often uglier.
To illustrate the value of such contradictions, consider that “Special cases aren’t special enough to break the rules.” - except for errors, which we are told “should never pass silently.”
Errors are dealt with in Python via exceptions - literally designed and named to deal with “special [or exceptional] cases.” In other words, this contradiction leads us to understand that, in order to hold both of these principles at once, we must make new rules, which our error handling can follow. In less rigidly dogmatic language, we are exhorted simultaneously to constrain our designs by some set of rules (presumably so we can better understand them), and to make sure that our designs explicitly handle errors. Ergo, those rules must contain rules for handling errors, which Python does via exceptions.
Leading back to line 3, “Simple is better than complex.”, I think we can evaluate the principles by their fruits; Python, by the standards of the Go language, is extremely complex. As Pike stated in his original talk about the language, his definition of “simple” is “Few keywords, parsable without a symbol table.”
I tend to think about simplicity as being multi-dimensional. Go is both simple in the implementation and fairly simple to learn, but not necessarily simple to use. As perhaps a counterpoint, Rust is not simple in implementation, learning, or use. Python does focus on simplicity, but more in learning and especially use than in implementation.
there’s no evasive, mr miyagi-like mystery here, they’re just talking about rules of thumb. “Beautiful is better than ugly” is not treated like a strange, otherworldly paradox. It’s merely trying to say “hey, caring about aesthetics is ok and you can do that”. Such a claim is so obvious today that we struggle to take it at face value and assume there must be some deeper, more profound double-meaning. Read the original thread and you find no such discussion. There’s no mysticism here, just people that found Python’s attitude to be a refreshing change from the utilitarianism of Java or the tedium of C.
I’m not arguing that Python is simple today. It’s not.
What I’m arguing is that Python had simplicity as a design goal thirty years ago when it was created, and ten years later when the principles that appear pep-20 were written. This statement:
Python, for example, doesn’t hold simplicity as a core value
Strikes me as very untrue. I think Python does hold simplicity as a core value, but that it has lost its way and become mired in baggage over the years. The pep-20 principles were written in 1999; the first commercial multi-core processor didn’t appear until 2 years later. A lot of what makes Python complicated today has to do with the difficulty of adding concurrency to a language long after the language was invented. How many people were trying to do async i/o in the application layer in 1999? kqueue didn’t even exist until the year after these principles were written. Even relatively straightforward Python constructs like the list comprehension didn’t exist in 1999; it was first proposed in pep-0202 the next year.
Errors are dealt with in Python via exceptions - literally designed and named to deal with “special [or exceptional] cases.”
What’s exceptional about an exception isn’t that the events that cause exceptions are out of the ordinary; it’s that the control flow of the program is out of the ordinary inasmuch as it doesn’t flow line-by-line. When you read until the end of a file, Python throws EOFError exception. Nobody really thinks (or has ever thought) that hitting the end of a file is a special case. It would be much more irregular to never hit the end of the file. Rather, the thing that is a special case is how control flow is handled for that event.
And of course, I’m not here to stan exceptions, I don’t like exceptions and I’m glad that a lot of people and a lot of ecosystems have moved on from them. Exceptions are among the biggest mistakes of language design that has cursed programming, second only to inheritance (or maybe null values).
Python, by the standards of the Go language, is extremely complex.
um, sure, but that’s not really a fair comparison, inasmuch as you’re comparing Python 3 after 30 years of development and Go 1 with about a decade’s worth of development. Python predates Go by at least 17 years. The community has learned a lot in that time, hardware and software have changed a lot in that time, and Go itself has the benefit of being able to learn from Python.
I tend to think about simplicity as being multi-dimensional.
ah, my favorite definition here comes from Rich Hickey’s talk Simple Made Easy.
I think most of what you’ve mentioned here is a worthy counterpoint to my comment; I don’t want to argue unduly. However, I would like to point out that this particular issue seems to me to be a microcosm of some larger ideas in the software industry. For example:
What’s exceptional about an exception isn’t that the events that cause exceptions are out of the ordinary; it’s that the control flow of the program is out of the ordinary inasmuch as it doesn’t flow line-by-line.
There are definitely a lot of people who think about exceptions this way; there are also a lot of people who don’t, including Bob Martin ( Clean Code, p109). Both my college CS curriculum and the training I underwent at my first internship talked about exceptions in the way I conceptualize them here. So, I think not only do we disagree about this, but many people do!
I think it’s a similar situation with the discussion of what “simple” means; Hickey’s definition is good, but misses out on some nuance of what we might want to call “simple”, or what some people do call “simple”.
um, sure, but that’s not really a fair comparison, inasmuch as you’re comparing Python 3 after 30 years of development and Go 1 with about a decade’s worth of development. Python predates Go by at least 17 years. The community has learned a lot in that time, hardware and software have changed a lot in that time, and Go itself has the benefit of being able to learn from Python.
I do agree with this - obviously, Python has evolved more than Go has. But, at the same time, Go’s team is explicitly trying to avoid adding features to the language, because their idea of simplicity lies within the implementation more than the usage of the language.
In any case, I’m not a big Go fan and I absolutely love Python (and even moreso Rust, which is much more complex!). All I’m saying is that “simple” is a hugely overloaded word and we have to be careful about making sure we understand how others are using it.
you’re comparing Python 3 after 30 years of development and Go 1 with about a decade’s worth of development. Python predates Go by at least 17 years
It’s also totally reasonable to claim that Go is tightly integrated with the lineage of C (sharing some of its designers) & some of the other languages Pike worked on/with at Bell/Lucent in the meantime (like Alef, Limbo, and newsqueak), all of which were attempts to simplify C while adding pipelining to coroutines as a first class feature of the language. In other words, you can say that rather than being a new language, Go is really the current point in a continuous line of development of C (in parallel with the current of standard C) in the same way that python3 is part of the continuous line of python development, & that it therefore should have benefit from the past 50 years of experience in language design. Certainly, I think some of the key people involved in Go consider it that way (from comments like “we wanted to fix the mistakes we made in the design of C” & from the strong similarities between Go and Limbo).
Yes it is mentioned as one of the guiding principles, but simplicity is not the overriding principle. The difference this decision can make is aptly summed in this (rather well known) essay.
Common misconception. UNIX was written in assembler, C was written on it, & then UNIX was rewritten in C.
and since it is standard on Windows to use file handling libraries that are written, in C or C++, for the Windows API.
You may be right, though I’ve never seen this done (and the file & stream handling facilities that are part of standard c – the ones you’d use if you were writing cross-platform code – are very UNIX-ish).
The point of this article, at least as I understood it, is that Go’s claims of “simplicity” aren’t well-founded.
He certainly mentions this in the opening paragraphs, but it didn’t seem like the focus of the rest. Certainly, in terms of language / stdlib implementation, the ‘simplest’ thing to do when running into a situation like file access where UNIX has a very rich interface and popular non-UNIX platforms have much less rich interfaces is to adopt the UNIX interface & use existing simulation facilities, especially if you come from a UNIX + C background (as the designers and authors of Go do).
Lua is marketed as simple, and exposes file i/o the same way – because it requires the least wrapper code.
Simulating UNIX (and common/conventional parts of UNIX environments) is so widespread that it’s done even in situations where it’s arguably more complicated. For instance, TK’s internal model is very close to X, and TK tries to wrap everything else to make it enough like X for all the facilities to still work, which caused some problems…
Lua exposes the standard C I/O module and nothing more. Last month I started working out what a platform independent directory API for Lua would look like (doing this on the mailing list). I wouldn’t say it was trivial but there was some form of mapping from POSIX to Windows (and some other less used systems these days) but what you end up with is the least common denominator. For instance, you can get similar information from Windows that you get from stat() on POSIX, but not all fields are available, nor are they named the same (and in the end, I think I ended up pleasing no one).
Then I tried abstracting I/O events and well … it can’t be done. POSIX is “when can I start IO? do IO” and Windows is “do IO. When is it done?” That difference in order makes it impossible to implement a unified API for events. You can do it with a framework, but a framework assumes control of the logic—it’s not an API. Implementing cross platform APIs are hard.
Go is controlled by Google [1]. Google is a Unix shop. Is it any wonder that it’s POSIX centric? Windows is a second class citizen at Google.
[1] Yeah yeah, open source, outside developers can contribute, etc. But in the end, what Google wants, Google gets. If Google doesn’t care, then yes, outsiders can influence the direction of a feature. But if it goes against how Google does things, no go.
Windows is a second class citizen in go (and lua, and all these other languages) because windows is a second-class citizen in c (the lowest common denominator for FFIs, bindings, writing portable standard libs) and a second-class citizen in the kinds of places that develop new languages (university CS departments, tech companies that scale big enough to want a new language & actually push it). Least common denominator for anything is usually a subset of unix functionality that has decent simulation on other platforms somebody’s already written and open sourced, because the other way around usually doesn’t exist & nobody knows about it even if it does. Windows provides posix emulation layers of varying quality & has for 30 years, same as beos did, same as mac os did before they actually became a unix, etc.
This probably isn’t a great thing in the long run. Unix is the best 1969 has to offer, & still beats out most of what 2020 has to offer, but arguably its popularity has prevented major improvements from happening or being adopted at scale simply because anything that totally breaks unix assumptions (rather than soft-breaking them and simply running slower with a workaround or something) will be rejected. No OS will become popular if it doesn’t have files and directories, unless it has something that can pretend to have a unix-style file & directory structure, at which point that’s all that will be used in portable code. Only the subset of permissions that correspond to unix permissions will be used. Only OSes with processes that have unique numeric IDs and can be killed will survive.
It’s a shame, but it’s not Go’s fault – and I take advantage of the fact that every popular modern operating system is either unix or a crappy approximation of unix every time I write code (no matter the language), because if the only OS features I use are the parts of unix that any freshman CS student is aware of, I will never need to write platform-specific code. I think we all do.
I think this blog post is correct, but also judging Go on things Go has never focused on being excellent at.
Go was intended to fix the issues that Google had with large C++ projects. It compiles fast. There are no cyclic dependencies. Concurrency is easy to use. Deployment is easy and straight-forward. The language is small (there are fewer keywords than in C).
Especially deployment is often disregarded when people are evaluating programming languages. Python, C and C++ has their strengths, but deployment can often be problematic. Compiling a static ELF in Rust is possible, and some may say this is a detail and not important, but it’s nevertheless not as straightforward as in Go.
Rust and Go has had completely different goals from day one, and that’s fine.
Compiling a static ELF in Rust is possible, and some may say this is a detail and not important, but it’s nevertheless not as straightforward as in Go.
What’s insufficiently static about Rust’s default behavior?
That the glibc dependency is dynamically linked and you need to use the musl target to statically link the C library? That C-interfacing -sys crates leave the linkage with C system libs dynamic?
tbh this whole package might not even be necessary any more. The time.Time method Sub now gives you a monotonic difference between two time.Time values that ignores changes in system clock time. This was added in Go 1.9, it’s mentioned here in the change notes. The original source for what you’re using predates the release of Go 1.9; it cites the issue that Go 1.9 closed, and the issue history in turn cites the library you’re using as an example of why the standard library needs fixing. So … that once-useful thing is now a part of the standard library, and I’m going to wager you can probably dispense with it entirely and just use the time package as-is. Maybe there’s something subtle going on here that I’m not noticing, but it looks like you can get some free dependency-shedding.
I think there’s a little more nuanace and truth to it than that, though.
If you encounter a language that’s deeply flawed but which you don’t have to use, you may make a funny blog post or whatever and move on with life. A language that is in active use every day, though, is probably going to be a continual topic.
In short, if you don’t care about something, you’re probably not going to waste a lot of ink on it.
I mean his long diatribe about path printing is an argument whose validity rests entirely on how technically correct it is, and … his argument about Go printing the wrong thing is technically incorrect, so … it really does seem more like complaining and less like insightful, useful, and actionable criticism.
As someone who has spent enough time writing Go code to have a say on this, I really hoped to support this article. I agree with the general premise: Go is hiding a lot more complexity under the rug than what the alleged promise of simplicity made us believe in. Also, as a language that touts for being simple and consistent, there are various and sometimes, aggravating inconsistencies in how Go gets things done.
Yet, the article moves on to some very in-depth examples of how the language fails at solving some very specific technical tasks. And, it goes on explaining for nearly half an hour. IMHO, the author would have achieved a much larger effect, had he either shortened the explanations, or chosen different examples, with a much broader scope. Of those, there are plenty out there.
It constantly lies about how complicated real-world systems are, and optimize for the 90% case,
Serious question, what’s wrong with optimizing for the 90% case?
ignoring correctness.
This is a bit of a weasel word. Does the author imply that in the remaining 10% of cases, Go is useless? (as in, the language cannot be used for these cases)
Or is it just that it’s slightly harder to work with?
They don’t mean the 90% use case, they mean the 90% execution case, and that Go omits a lot of features that enable the programmer to reason confidently about the correctness of their edge case handling. The rhetoric of simplicity around Go implies (disingenuously, IMO) that the language’s simplicity indicates a reduction of the total complexity involved in solving some problem, but more commonly it just shifts the complexity elsewhere, usually to a place (runtime behavior) where the complexity is harder to characterize and more dangerous to get wrong. I agree with the author that this is a remarkably poor tradeoff, and it’s definitely been my experience in writing Go daily for more than a year.
The rhetoric of simplicity around Go implies (disingenuously, IMO) that the language’s simplicity indicates a reduction of the total complexity involved in solving some problem
I’ve always thought that the type of simplicity they’re talking about isn’t about making complex problems simpler, but about getting the language out of the way when solving complex problems. You don’t have to reason about new constructs or different patterns when you’re solving a different problem. It’s also rather easy to read unfamiliar Go code because there aren’t very many ways to get clever.
That said I’m quite new to Go, and so far I’ve only used it for cloud-hosted server apps where my goals and use-cases align well with the Go developers’.
A really good point. That’s why I’ve been telling people to “panic without worry”. Not that I am against error checking. Where it makes sense, you should. It’s just the nature of the language that pushes you to find solutions to things like resilience at the infrastructure layer.
I always wonder why Go gets the fame for this as if it’s the only language that can do this. You can statically link a C or C++ program. It’s not the default, but you can. There’s definitely something to be said for good defaults and how they encourage behaviour, but I’m still puzzled as to how Go managed to get this particular slice of the marketing pie.
when you make something simple, you move complexity elsewhere
This is a very good point, and one I’ve made before. Whatever logic your program needs to do is still complicated. Making the language simple just makes user code have to do more of the work. There’s a sweet spot, but that lies on different points in the spectrum for different people. Haskell is a step too far for me, and I like Haskell.
You can statically link a C or C++ program. It’s not the default, but you can.
Defaults matter, especially in cases where you want to use others’ libraries, because there’s a decent chance that the non-default configuration doesn’t work. For example, glibc doesn’t support static linking at all, and more than a few other libraries only work with glibc.
I agree that defaults matter. That’s why I don’t use Go. The fact that print still exists, but prints to stderr is craptastic.
Imagine if you asked for a knife to chop things for a salad, and someone handed over a simple kitchen knife. As you start to slice the tomato, you realize something is terribly wrong. You feel an edge blunter than a butter knife. You turn around to your friend confused, and they smack their forehead and go “ohhhhh, for a tomato! Hold on.” And they wander off to another room and bring back a chef’s block with a nearly identical set of actually sharp knives. “You just have to go get these from the pantry when you want actual sharp knives. We don’t keep them in the kitchen, for obvious reasons.”
As a noob to the language, I was tinkering with some code. At one point, flipping between python and Go, I put a print() statement in Go. I then spent the next couple of minutes confused about why the next program in the pipe couldn’t see the output from my Go.
For those that have never used Go or are just starting out: you need to import “fmt”, and then use fmt.Print. Yes, it’s in all the tutorials. Yes, you will likely still beans it up and use print() by accident, especially if you come from other languages. No, there aren’t any warnings when you do this and then go run or go build. Go just does something different for the concept of “print” and “println” in silence. I don’t need that kind “tee hee, GOTCHA!” in my life.
I always wonder why Go gets the fame for this as if it’s the only language that can do this. You can statically link a C or C++ program. It’s not the default, but you can. There’s definitely something to be said for good defaults and how they encourage behaviour, but I’m still puzzled as to how Go managed to get this particular slice of the marketing pie.
My theory is that it’s because Ruby, Python, and Node lowered the bar so tremendously on this that anything else is seen as a huge leap forward.
and the c/c++ toolchains are not exactly easy to handle. i won’t even try to build something static that wasn’t intended to. that’s why many things which are distributed as binary use linker magic and ship their own set of libs.
I like the fact that the author had concrete examples to prove his point although I’m not surprised by the conclusion. Golang developers are one of the most enthusiastic bunch I’ve ever seen and some of them very recently realize that Go has been and will always be Google’s language. Most of the capabilities of the language are for supporting Google’s usecases and it works perfectly well for them. For e.g. a successful project like k8s is going to be run on some flavor of Unix, largely Linux in fact and Golang supports it. Windows is and will always be a second-class citizen.
Comparing Golang and Rust is not fair, IMO. Yes, there’s overlap to the domains these languages were invented to address but Golang is immensely simple and easy for a quick webservice that can do very specific web server stuff. Yes, there are projects like cockroachdb, docker etc. which are more “system-sy” but you lose platform agnostic behavior once you start leveraging these.
Go seems to at least understand not getting everything goarista depends on if your repo doesn’t need it, which is good. go list -m all prints the full dependency list too, and it’s much smaller with the first main.go. This is not meant to distract from anything else pointed out here, I think most if not all if it are very valid criticisms.
(Can I just point out how hilarious that “Extension” was deemed long enough to abbreviate to “Ext”, but “IsPathSeparator” wasn’t?)
Rust fan complaining about weird abbreviations. Now that is hilarious.
The reality is that Rust is overengineered in all the wrong places. It has all this complicated functionality for getting file extensions, that’s clearly been well thought out (and is a bit overengineered, I think), but can’t even get the BASICS right! Example:
The type returned by the command line arguments function? String. What’s a common command line argument? A path. What did we just establish cannot be assumed to always be valid UTF-8? Paths. What are strings in Rust? UTF-8 byte sequences. What are strings in Go? Arbitrary byte sequences. Not necessarily UTF-8. Bit of an own goal there, mate.
In practice, it gets old very quickly. You now have related code split across multiple files, even if only one of the functions is platform-specific.
In other words, Go forces you to split up your code properly by putting different OS-specific code in different files rather than filling files with platform-specific code in odd places. Separate files for Windows and Unix functionality is a better idea in any language.
env::args does return Strings for the common case but if you need to handle arguments that are expected not to be UTF-8 there is env::args_os which returns OsStrings. Path can be created from this and String as it’s constructor is generic over AsRef<OsStr>.
This is a good point actually - what happens on common operating systems if you try to pass non-UTF8 strings as the arguments to an executable? I’m not actually sure.
Looking at the rust documentation for env::args at https://doc.rust-lang.org/std/env/fn.args.html , I notice the text “The returned iterator will panic during iteration if any argument to the process is not valid unicode. If this is not desired, use the args_os function instead.”, which honestly strikes me as a fairly important gotcha, and it’s one I wasn’t consciously aware of. According to the source code for the Args struct, it internally contains the more-general ArgsOs struct that can handle non-unicode arguments without crashing.
There’s other places in the rust standard library where using code in a “default” way will panic on the wrong kinds of inputs, and usually there’s a slightly more complicated way to explicitly handle those erroneous inputs in code. That being said, I tend to be opposed to this kind of API design. Even though panicking is well-defined behavior, I am more inclined to design APIs that bubble up error values to the caller, rather than panicking on their behalf, even if a common way that a library client might deal with the bubbled-up error is explicitly panicking on it.
cool bears hot take: time to reimplement everything in rust, where the pastures are really green. after all it’s a mozilla fantasy, i bet its a good one.
with that said:
at least half of the things regarding file/path/whatever handling are documented to assume unix-like things. if this was a problem, someone would bother to build a nicer package for file(name) handling?
leading to the second point, a 3rd party package imports too many things.. don’t use it? roll your own idle-timeout? write it in rust and call it from go?
the real problem is that we still have physical systems beneath our magic abstractions, and the boundaries to those tend to be either simplifications (like in go) or over-abstractions trying to be usable in every case. i tend to think that trying to map every case isn’t worth the work.
most of them are unixoid, but you could also ask: where is the support for the ACLs on these systems. i bet there are differences in what is possible with them, while being still in the same family of operating systems. i don’t know it, but what happens in rust when a file is set +i on linux, but the permission bits don’t reflect that?
as developer you should usually know that windows and unix-like systems are sufficiently different with regard to file modes. it’s written down in the docs. i suppose one could be bothered to go an extra mile for windows and use special handling for it, maybe.
the whole concept of blub is predicated on PG’s arrogance, and the whole concept of lobste.rs is to escape the arrogance of hacker news so … this is kinda the wrong audience for that comment?
Somewhat agree, but I think the concept of “blub” is still useful (maybe there’s another name for it?), and it predates the season of arrogance as far as I can tell.
Ah, yes. The old “this person has the same opinion that some other people have, and I disagreed with them, so I can’t possibly learn anything new from this person’s perspective”, well known for being both correct and insightful.
Though I’m conscious of the pitfalls of confirmation bias, it’s still good to see others echoing the reasons I’ve chosen to invest in Rust, despite the substantially steeper learning curve.
Good rant, but where’s the beautiful Rust code to demonstrate how its standard library handles HTTP timeouts perfectly?
The article has a technical inaccuracy with respect to Go’s path printing. It claims that Go prints the “wrong path”, but it prints exactly the bytes in the file name. This can be confirmed with
strace -xx
:It’s unclear what they mean by “wrong” but I guess they don’t want to it to be so easy to write non-utf-8 encoded bytes to standard out?
I also disagree with the characterization of Go saying “don’t worry about encodings! things are probably utf-8”. There’s exactly two spots in the entire language that tie strings with utf-8, and that’s when you range over a string or do conversions with []rune. In every other spot, they are byte sequences.
This post makes some valid points, but it’s complaining about things that apply to almost all major languages.
Like, the first third of the post is simply saying that platform-agnostic stuff assumes a unix-like interface, and then simulates it if it doesn’t exist. Which is… exactly what c does, and exactly what python does, and exactly what java does, and exactly what nodejs does, and exactly what tcl does. You can theoretically design a totally new synthetic interface that exposes all the features somebody might possibly want & then simulate it on all other platforms, but then you’ve alienated everybody who has ever used a different programming language. Or you can do some kind of windows-first thing & simulate windows behavior on non-windows systems, but even stuff developed at microsoft doesn’t tend to do that.
The last third is complaining that go packages can have arbitrarily large dependency trees. This is true, and it’s a problem. It’s also endemic across every language with its own package management ecosystem, & it’s hard to find packages that don’t pull in the whole universe when dependencies are automatically pulled in. (In languages where automatic dependency management is rare, dependencies tend to stand alone more often, simply because if they required too much nobody would bother to use them. In either case, it’s a matter of package maintainers having varying levels of sloppiness!)
In other words, it’s a little weird for this post to be framed as an exhortation against using go, and not a complaint about the norms of modern languages! Everything it says about go also applies to python and javascript.
While true, I’m pretty sure the OP was making a different point. The OP was using a package that imported
github.com/aristanetworks/goarista/monotime
located at https://github.com/aristanetworks/goarista/tree/master/monotime, but as a consequence, it also pulled in everything else in that repo, even thoughmonotime
doesn’t explicitly depend on any of it. You can try this out yourself:As far as I can tell, it’s just grabbing everything from the repo’s
go.mod
file: https://github.com/aristanetworks/goarista/blob/master/go.modNow, when I run
go build
, it doesn’t actually seem to download/build everything that’s ingo.sum
. So perhaps the problem isn’t as bad as it appears, but thatgo.sum
file is still pretty scary.(I’ve also run into annoying problems similarish to this, where it’s impossible to specify that a dependency is only used in tests. But I guess that goes back to the whole “simplicity” question…)
That’s certainly a problem. It’s not Rob Pike’s problem (or the Go development team’s problem) that a popular third party package was sloppy about minimizing its dependencies (although the decision to have automatic dependency management tooling ship with the language makes it more likely for this to happen, as does sheer popularity). Go doesn’t seem to be unusual in this either, though I’m sure the static-build-by-default setting makes it more visible in the form of large binaries.
I think the point here is that the Go package manager makes it very easy to be sloppy in this regard. AFAIK, other language package managers do not have this very specific type of problem.
These languages, though, don’t market themselves as “simple” - and at least in the case of C, it’s kind of dubious to say that it “assumes a UNIX-like interface, and then simulates it if it doesn’t exist”, since at its inception there was no such thing as UNIX, and since it is standard on Windows to use file handling libraries that are written, in C or C++, for the Windows API.
The point of this article, at least as I understood it, is that Go’s claims of “simplicity” aren’t well-founded. Python, for example, doesn’t hold simplicity as a core value, either in interface or in implementation. Obviousness, clarity, etc, but not simplicity.
… “simple is better than complex” is literally the third thing in pep-20 https://www.python.org/dev/peps/pep-0020/#the-zen-of-python
Isn’t the point of the Zen of Python being written the way it is to invite discussion of the nuances and interactions and inherent contradictions between these statements?
I believe so, and this is clearly unlike the Go team’s philosophy regarding “simplicity”.
huh, I never really read it that way. I never really found it very Zen-like, the usage of Zen in this context honestly seems … just like weirdly appropriative? They’re not exactly mysteries or parables, they’re just short statements of values.
do you read any of the other statements in the Zen of Python as contradicting “simple is better than complex”? My time programming Python (years ago I was a professional Python programmer but I don’t use Python much any more) definitely made me feel that simplicity was a stated value of the language and the ecosystem, although the follow-through on that value was … somewhat inconsistently applied.
It’s perhaps appropriative, though not really weirdly so; there’s a practice in Zen of recitation and meditation on the meaning of collections of koan, each of which is a small, relatively simple — at first blush —statement generally about a single philosophical truth, but it’s the whole that shows the ebb and flow between each individual statement. They don’t have to be either parables or mysteries, merely a set of things that push and pull on each other, like the wind blowing the bamboo about.
So “simple is better than complex” doesn’t need contradiction, but it is balanced by “complex is better than complicated” and given more flavor by “sparse is better than dense” … in all things lean towards the left of each of the first six statements, but accept that nature of the problem may push you towards the right… something can necessarily be the far right on all six and still be Pythonic, but only if the problem (and not our own vanity) requires it.
So simplicity is a virtue, relative to complexity, but complexity is a virtue relative to complication, and so on.
As appropriations of Zen go it’s not all that weird, and also not that far off the mark.
Sure, PEP 20 is full of contradictions, which I think we are meant to contemplate productively (though I don’t disagree with you that “Zen” is perhaps going a bit far.) Consider even just lines 1 and 2:
The beautiful interface is often the one that hides some of the ugliness inherent in the domain; the explicit interface is often uglier.
To illustrate the value of such contradictions, consider that “Special cases aren’t special enough to break the rules.” - except for errors, which we are told “should never pass silently.”
Errors are dealt with in Python via exceptions - literally designed and named to deal with “special [or exceptional] cases.” In other words, this contradiction leads us to understand that, in order to hold both of these principles at once, we must make new rules, which our error handling can follow. In less rigidly dogmatic language, we are exhorted simultaneously to constrain our designs by some set of rules (presumably so we can better understand them), and to make sure that our designs explicitly handle errors. Ergo, those rules must contain rules for handling errors, which Python does via exceptions.
Leading back to line 3, “Simple is better than complex.”, I think we can evaluate the principles by their fruits; Python, by the standards of the Go language, is extremely complex. As Pike stated in his original talk about the language, his definition of “simple” is “Few keywords, parsable without a symbol table.”
I tend to think about simplicity as being multi-dimensional. Go is both simple in the implementation and fairly simple to learn, but not necessarily simple to use. As perhaps a counterpoint, Rust is not simple in implementation, learning, or use. Python does focus on simplicity, but more in learning and especially use than in implementation.
this seems like a huge reach. read the original thread that prompted pep-20: https://groups.google.com/forum/#!topic/comp.lang.python/B_VxeTBClM0%5B126-150%5D
there’s no evasive, mr miyagi-like mystery here, they’re just talking about rules of thumb. “Beautiful is better than ugly” is not treated like a strange, otherworldly paradox. It’s merely trying to say “hey, caring about aesthetics is ok and you can do that”. Such a claim is so obvious today that we struggle to take it at face value and assume there must be some deeper, more profound double-meaning. Read the original thread and you find no such discussion. There’s no mysticism here, just people that found Python’s attitude to be a refreshing change from the utilitarianism of Java or the tedium of C.
I’m not arguing that Python is simple today. It’s not.
What I’m arguing is that Python had simplicity as a design goal thirty years ago when it was created, and ten years later when the principles that appear pep-20 were written. This statement:
Strikes me as very untrue. I think Python does hold simplicity as a core value, but that it has lost its way and become mired in baggage over the years. The pep-20 principles were written in 1999; the first commercial multi-core processor didn’t appear until 2 years later. A lot of what makes Python complicated today has to do with the difficulty of adding concurrency to a language long after the language was invented. How many people were trying to do async i/o in the application layer in 1999? kqueue didn’t even exist until the year after these principles were written. Even relatively straightforward Python constructs like the list comprehension didn’t exist in 1999; it was first proposed in pep-0202 the next year.
What’s exceptional about an exception isn’t that the events that cause exceptions are out of the ordinary; it’s that the control flow of the program is out of the ordinary inasmuch as it doesn’t flow line-by-line. When you read until the end of a file, Python throws EOFError exception. Nobody really thinks (or has ever thought) that hitting the end of a file is a special case. It would be much more irregular to never hit the end of the file. Rather, the thing that is a special case is how control flow is handled for that event.
And of course, I’m not here to stan exceptions, I don’t like exceptions and I’m glad that a lot of people and a lot of ecosystems have moved on from them. Exceptions are among the biggest mistakes of language design that has cursed programming, second only to inheritance (or maybe null values).
um, sure, but that’s not really a fair comparison, inasmuch as you’re comparing Python 3 after 30 years of development and Go 1 with about a decade’s worth of development. Python predates Go by at least 17 years. The community has learned a lot in that time, hardware and software have changed a lot in that time, and Go itself has the benefit of being able to learn from Python.
ah, my favorite definition here comes from Rich Hickey’s talk Simple Made Easy.
I think most of what you’ve mentioned here is a worthy counterpoint to my comment; I don’t want to argue unduly. However, I would like to point out that this particular issue seems to me to be a microcosm of some larger ideas in the software industry. For example:
There are definitely a lot of people who think about exceptions this way; there are also a lot of people who don’t, including Bob Martin ( Clean Code, p109). Both my college CS curriculum and the training I underwent at my first internship talked about exceptions in the way I conceptualize them here. So, I think not only do we disagree about this, but many people do!
I think it’s a similar situation with the discussion of what “simple” means; Hickey’s definition is good, but misses out on some nuance of what we might want to call “simple”, or what some people do call “simple”.
I do agree with this - obviously, Python has evolved more than Go has. But, at the same time, Go’s team is explicitly trying to avoid adding features to the language, because their idea of simplicity lies within the implementation more than the usage of the language.
In any case, I’m not a big Go fan and I absolutely love Python (and even moreso Rust, which is much more complex!). All I’m saying is that “simple” is a hugely overloaded word and we have to be careful about making sure we understand how others are using it.
It’s also totally reasonable to claim that Go is tightly integrated with the lineage of C (sharing some of its designers) & some of the other languages Pike worked on/with at Bell/Lucent in the meantime (like Alef, Limbo, and newsqueak), all of which were attempts to simplify C while adding pipelining to coroutines as a first class feature of the language. In other words, you can say that rather than being a new language, Go is really the current point in a continuous line of development of C (in parallel with the current of standard C) in the same way that python3 is part of the continuous line of python development, & that it therefore should have benefit from the past 50 years of experience in language design. Certainly, I think some of the key people involved in Go consider it that way (from comments like “we wanted to fix the mistakes we made in the design of C” & from the strong similarities between Go and Limbo).
Yes it is mentioned as one of the guiding principles, but simplicity is not the overriding principle. The difference this decision can make is aptly summed in this (rather well known) essay.
Common misconception. UNIX was written in assembler, C was written on it, & then UNIX was rewritten in C.
You may be right, though I’ve never seen this done (and the file & stream handling facilities that are part of standard c – the ones you’d use if you were writing cross-platform code – are very UNIX-ish).
He certainly mentions this in the opening paragraphs, but it didn’t seem like the focus of the rest. Certainly, in terms of language / stdlib implementation, the ‘simplest’ thing to do when running into a situation like file access where UNIX has a very rich interface and popular non-UNIX platforms have much less rich interfaces is to adopt the UNIX interface & use existing simulation facilities, especially if you come from a UNIX + C background (as the designers and authors of Go do).
Lua is marketed as simple, and exposes file i/o the same way – because it requires the least wrapper code.
Simulating UNIX (and common/conventional parts of UNIX environments) is so widespread that it’s done even in situations where it’s arguably more complicated. For instance, TK’s internal model is very close to X, and TK tries to wrap everything else to make it enough like X for all the facilities to still work, which caused some problems…
Lua exposes the standard C I/O module and nothing more. Last month I started working out what a platform independent directory API for Lua would look like (doing this on the mailing list). I wouldn’t say it was trivial but there was some form of mapping from POSIX to Windows (and some other less used systems these days) but what you end up with is the least common denominator. For instance, you can get similar information from Windows that you get from
stat()
on POSIX, but not all fields are available, nor are they named the same (and in the end, I think I ended up pleasing no one).Then I tried abstracting I/O events and well … it can’t be done. POSIX is “when can I start IO? do IO” and Windows is “do IO. When is it done?” That difference in order makes it impossible to implement a unified API for events. You can do it with a framework, but a framework assumes control of the logic—it’s not an API. Implementing cross platform APIs are hard.
Go is controlled by Google [1]. Google is a Unix shop. Is it any wonder that it’s POSIX centric? Windows is a second class citizen at Google.
[1] Yeah yeah, open source, outside developers can contribute, etc. But in the end, what Google wants, Google gets. If Google doesn’t care, then yes, outsiders can influence the direction of a feature. But if it goes against how Google does things, no go.
Yup. That’s sort of what I’m getting at.
Windows is a second class citizen in go (and lua, and all these other languages) because windows is a second-class citizen in c (the lowest common denominator for FFIs, bindings, writing portable standard libs) and a second-class citizen in the kinds of places that develop new languages (university CS departments, tech companies that scale big enough to want a new language & actually push it). Least common denominator for anything is usually a subset of unix functionality that has decent simulation on other platforms somebody’s already written and open sourced, because the other way around usually doesn’t exist & nobody knows about it even if it does. Windows provides posix emulation layers of varying quality & has for 30 years, same as beos did, same as mac os did before they actually became a unix, etc.
This probably isn’t a great thing in the long run. Unix is the best 1969 has to offer, & still beats out most of what 2020 has to offer, but arguably its popularity has prevented major improvements from happening or being adopted at scale simply because anything that totally breaks unix assumptions (rather than soft-breaking them and simply running slower with a workaround or something) will be rejected. No OS will become popular if it doesn’t have files and directories, unless it has something that can pretend to have a unix-style file & directory structure, at which point that’s all that will be used in portable code. Only the subset of permissions that correspond to unix permissions will be used. Only OSes with processes that have unique numeric IDs and can be killed will survive.
It’s a shame, but it’s not Go’s fault – and I take advantage of the fact that every popular modern operating system is either unix or a crappy approximation of unix every time I write code (no matter the language), because if the only OS features I use are the parts of unix that any freshman CS student is aware of, I will never need to write platform-specific code. I think we all do.
I think this blog post is correct, but also judging Go on things Go has never focused on being excellent at.
Go was intended to fix the issues that Google had with large C++ projects. It compiles fast. There are no cyclic dependencies. Concurrency is easy to use. Deployment is easy and straight-forward. The language is small (there are fewer keywords than in C).
Especially deployment is often disregarded when people are evaluating programming languages. Python, C and C++ has their strengths, but deployment can often be problematic. Compiling a static ELF in Rust is possible, and some may say this is a detail and not important, but it’s nevertheless not as straightforward as in Go.
Rust and Go has had completely different goals from day one, and that’s fine.
What’s insufficiently static about Rust’s default behavior?
That the glibc dependency is dynamically linked and you need to use the musl target to statically link the C library? That C-interfacing
-sys
crates leave the linkage with C system libs dynamic?By default all Rust code is statically linked.
This looks dynamically linked to me:
It’s dynamically linked with glibc, but surely all the Rust code is statically linked into the executable by default.
I know this is slightly tangential to your post, but the dependency issue in getlantern/idletiming is getting a fix: https://github.com/getlantern/mtime/pull/2
FWIW, thanks for pointing it out :D
tbh this whole package might not even be necessary any more. The
time.Time
methodSub
now gives you a monotonic difference between twotime.Time
values that ignores changes in system clock time. This was added in Go 1.9, it’s mentioned here in the change notes. The original source for what you’re using predates the release of Go 1.9; it cites the issue that Go 1.9 closed, and the issue history in turn cites the library you’re using as an example of why the standard library needs fixing. So … that once-useful thing is now a part of the standard library, and I’m going to wager you can probably dispense with it entirely and just use thetime
package as-is. Maybe there’s something subtle going on here that I’m not noticing, but it looks like you can get some free dependency-shedding.[Comment removed by author]
I’m pretty sure he didn’t mean it to comment on individual criticism, but more that adoption also brings widespread discussion about flaws.
I think there’s a little more nuanace and truth to it than that, though.
If you encounter a language that’s deeply flawed but which you don’t have to use, you may make a funny blog post or whatever and move on with life. A language that is in active use every day, though, is probably going to be a continual topic.
In short, if you don’t care about something, you’re probably not going to waste a lot of ink on it.
Marking critique as whining is probably some sort of a logical fallacy.
I mean his long diatribe about path printing is an argument whose validity rests entirely on how technically correct it is, and … his argument about Go printing the wrong thing is technically incorrect, so … it really does seem more like complaining and less like insightful, useful, and actionable criticism.
As someone who has spent enough time writing Go code to have a say on this, I really hoped to support this article. I agree with the general premise: Go is hiding a lot more complexity under the rug than what the alleged promise of simplicity made us believe in. Also, as a language that touts for being simple and consistent, there are various and sometimes, aggravating inconsistencies in how Go gets things done.
Yet, the article moves on to some very in-depth examples of how the language fails at solving some very specific technical tasks. And, it goes on explaining for nearly half an hour. IMHO, the author would have achieved a much larger effect, had he either shortened the explanations, or chosen different examples, with a much broader scope. Of those, there are plenty out there.
Somewhat unrelated but I really like the “Cool bear’s hot tip” sections. It’s a very cute bear
Serious question, what’s wrong with optimizing for the 90% case?
This is a bit of a weasel word. Does the author imply that in the remaining 10% of cases, Go is useless? (as in, the language cannot be used for these cases)
Or is it just that it’s slightly harder to work with?
They don’t mean the 90% use case, they mean the 90% execution case, and that Go omits a lot of features that enable the programmer to reason confidently about the correctness of their edge case handling. The rhetoric of simplicity around Go implies (disingenuously, IMO) that the language’s simplicity indicates a reduction of the total complexity involved in solving some problem, but more commonly it just shifts the complexity elsewhere, usually to a place (runtime behavior) where the complexity is harder to characterize and more dangerous to get wrong. I agree with the author that this is a remarkably poor tradeoff, and it’s definitely been my experience in writing Go daily for more than a year.
I’ve always thought that the type of simplicity they’re talking about isn’t about making complex problems simpler, but about getting the language out of the way when solving complex problems. You don’t have to reason about new constructs or different patterns when you’re solving a different problem. It’s also rather easy to read unfamiliar Go code because there aren’t very many ways to get clever.
That said I’m quite new to Go, and so far I’ve only used it for cloud-hosted server apps where my goals and use-cases align well with the Go developers’.
A really good point. That’s why I’ve been telling people to “panic without worry”. Not that I am against error checking. Where it makes sense, you should. It’s just the nature of the language that pushes you to find solutions to things like resilience at the infrastructure layer.
Great write-up.
I always wonder why Go gets the fame for this as if it’s the only language that can do this. You can statically link a C or C++ program. It’s not the default, but you can. There’s definitely something to be said for good defaults and how they encourage behaviour, but I’m still puzzled as to how Go managed to get this particular slice of the marketing pie.
This is a very good point, and one I’ve made before. Whatever logic your program needs to do is still complicated. Making the language simple just makes user code have to do more of the work. There’s a sweet spot, but that lies on different points in the spectrum for different people. Haskell is a step too far for me, and I like Haskell.
Defaults matter, especially in cases where you want to use others’ libraries, because there’s a decent chance that the non-default configuration doesn’t work. For example, glibc doesn’t support static linking at all, and more than a few other libraries only work with glibc.
I agree, which is why I mentioned how good defaults encourage good behaviour.
I agree that defaults matter. That’s why I don’t use Go. The fact that print still exists, but prints to stderr is craptastic.
Imagine if you asked for a knife to chop things for a salad, and someone handed over a simple kitchen knife. As you start to slice the tomato, you realize something is terribly wrong. You feel an edge blunter than a butter knife. You turn around to your friend confused, and they smack their forehead and go “ohhhhh, for a tomato! Hold on.” And they wander off to another room and bring back a chef’s block with a nearly identical set of actually sharp knives. “You just have to go get these from the pantry when you want actual sharp knives. We don’t keep them in the kitchen, for obvious reasons.”
As a noob to the language, I was tinkering with some code. At one point, flipping between python and Go, I put a print() statement in Go. I then spent the next couple of minutes confused about why the next program in the pipe couldn’t see the output from my Go.
For those that have never used Go or are just starting out: you need to import “fmt”, and then use fmt.Print. Yes, it’s in all the tutorials. Yes, you will likely still beans it up and use print() by accident, especially if you come from other languages. No, there aren’t any warnings when you do this and then go run or go build. Go just does something different for the concept of “print” and “println” in silence. I don’t need that kind “tee hee, GOTCHA!” in my life.
Maybe Go could use some of Ruby’s POLA philosophy.
My theory is that it’s because Ruby, Python, and Node lowered the bar so tremendously on this that anything else is seen as a huge leap forward.
and the c/c++ toolchains are not exactly easy to handle. i won’t even try to build something static that wasn’t intended to. that’s why many things which are distributed as binary use linker magic and ship their own set of libs.
You can statically link C and C++, yes.
But with Go you can also cross-compile that static executable with zero effort to multiple platforms.
What does Cross compilation have anything to do with static linking or what I said?
I like the fact that the author had concrete examples to prove his point although I’m not surprised by the conclusion. Golang developers are one of the most enthusiastic bunch I’ve ever seen and some of them very recently realize that Go has been and will always be Google’s language. Most of the capabilities of the language are for supporting Google’s usecases and it works perfectly well for them. For e.g. a successful project like k8s is going to be run on some flavor of Unix, largely Linux in fact and Golang supports it. Windows is and will always be a second-class citizen.
Comparing Golang and Rust is not fair, IMO. Yes, there’s overlap to the domains these languages were invented to address but Golang is immensely simple and easy for a quick webservice that can do very specific web server stuff. Yes, there are projects like cockroachdb, docker etc. which are more “system-sy” but you lose platform agnostic behavior once you start leveraging these.
I was surprised to see goarista here, but that’s what we get for having a monorepo with stuff that other people have apparently found useful :)
This made me a bit curious and did a tiny amount of experimentation:
Go seems to at least understand not getting everything goarista depends on if your repo doesn’t need it, which is good.
go list -m all
prints the full dependency list too, and it’s much smaller with the first main.go. This is not meant to distract from anything else pointed out here, I think most if not all if it are very valid criticisms.Rust fan complaining about weird abbreviations. Now that is hilarious.
The reality is that Rust is overengineered in all the wrong places. It has all this complicated functionality for getting file extensions, that’s clearly been well thought out (and is a bit overengineered, I think), but can’t even get the BASICS right! Example:
The type returned by the command line arguments function? String. What’s a common command line argument? A path. What did we just establish cannot be assumed to always be valid UTF-8? Paths. What are strings in Rust? UTF-8 byte sequences. What are strings in Go? Arbitrary byte sequences. Not necessarily UTF-8. Bit of an own goal there, mate.
In other words, Go forces you to split up your code properly by putting different OS-specific code in different files rather than filling files with platform-specific code in odd places. Separate files for Windows and Unix functionality is a better idea in any language.
env::args
does return Strings for the common case but if you need to handle arguments that are expected not to be UTF-8 there is env::args_os which returns OsStrings.Path
can be created from this and String as it’s constructor is generic overAsRef<OsStr>
.[Comment from banned user removed]
[Comment removed by author]
This is a good point actually - what happens on common operating systems if you try to pass non-UTF8 strings as the arguments to an executable? I’m not actually sure.
Looking at the rust documentation for
env::args
at https://doc.rust-lang.org/std/env/fn.args.html , I notice the text “The returned iterator will panic during iteration if any argument to the process is not valid unicode. If this is not desired, use the args_os function instead.”, which honestly strikes me as a fairly important gotcha, and it’s one I wasn’t consciously aware of. According to the source code for the Args struct, it internally contains the more-generalArgsOs
struct that can handle non-unicode arguments without crashing.There’s other places in the rust standard library where using code in a “default” way will panic on the wrong kinds of inputs, and usually there’s a slightly more complicated way to explicitly handle those erroneous inputs in code. That being said, I tend to be opposed to this kind of API design. Even though panicking is well-defined behavior, I am more inclined to design APIs that bubble up error values to the caller, rather than panicking on their behalf, even if a common way that a library client might deal with the bubbled-up error is explicitly panicking on it.
cool bears hot take: time to reimplement everything in rust, where the pastures are really green. after all it’s a mozilla fantasy, i bet its a good one.
with that said:
at least half of the things regarding file/path/whatever handling are documented to assume unix-like things. if this was a problem, someone would bother to build a nicer package for file(name) handling?
leading to the second point, a 3rd party package imports too many things.. don’t use it? roll your own idle-timeout? write it in rust and call it from go?
Ahh, good old PHP-like approach to interface design…
well, php still has a big marketshare 🤷🏼♂️
the real problem is that we still have physical systems beneath our magic abstractions, and the boundaries to those tend to be either simplifications (like in go) or over-abstractions trying to be usable in every case. i tend to think that trying to map every case isn’t worth the work.
just look at the amount of operating systems go supports: https://build.golang.org
most of them are unixoid, but you could also ask: where is the support for the ACLs on these systems. i bet there are differences in what is possible with them, while being still in the same family of operating systems. i don’t know it, but what happens in rust when a file is set +i on linux, but the permission bits don’t reflect that?
as developer you should usually know that windows and unix-like systems are sufficiently different with regard to file modes. it’s written down in the docs. i suppose one could be bothered to go an extra mile for windows and use special handling for it, maybe.
Refreshing to see an entrenched Golang user break free and come clean about the true realities that the other blubbites appear blind to.
My tldr: Go makes the programmer do the computer’s job.
Wait, we’re doing the “unpopular opinion” thing again, right?
/ducks
the whole concept of blub is predicated on PG’s arrogance, and the whole concept of lobste.rs is to escape the arrogance of hacker news so … this is kinda the wrong audience for that comment?
Somewhat agree, but I think the concept of “blub” is still useful (maybe there’s another name for it?), and it predates the season of arrogance as far as I can tell.
Man likes Rust more than Go. News at 11.
Nothing new here.
Ah, yes. The old “this person has the same opinion that some other people have, and I disagreed with them, so I can’t possibly learn anything new from this person’s perspective”, well known for being both correct and insightful.
Though I’m conscious of the pitfalls of confirmation bias, it’s still good to see others echoing the reasons I’ve chosen to invest in Rust, despite the substantially steeper learning curve.
[Comment from banned user removed]
you’ve double-commented
[Comment removed by author]
I think I may have found a typo, but really enjoyed the article. Thanks!
[Comment removed by author]