
A Rust Contributor Tries Their Hand at Go - Manishearth
http://www.polyglotweekly.com/2015/04/24/thoughts-of-a-rustacean-learning-go.html
======
zuzun
I really enjoyed this review. You can tell the author spent some time trying
to spot the good and bad things about Go. Too many Go reviews turn out to be:
Watch me trying to use Go as a functional programming language and fail
miserably because it lacks generics.

Anyway, just like the author I found myself fighting the forced directory
structure at first, but it's something I started to like about Go: Go doesn't
come with a complicated package management. All packages lie in a directory
and you can edit everything with normal shell commands. This keeps things
refreshingly simple.

~~~
wyager
>Watch me trying to use Go as a functional programming language and fail
miserably because it lacks generics.

Go's lack of generics and its incapacity for functional programming are mostly
unrelated.

~~~
innguest
Except for the tiny fact that you don't get higher-kinded types, which is huge
for functional programming.

~~~
wyager
HKTs aren't even remotely necessary for basic functional programming.

HKTs are very useful for e.g. typeclasses (used for Monad, Functor, etc.
definitions) but you don't need them. Strictly speaking, you don't even need
types at all (even though they're great).

~~~
innguest
That's totally right which is why I never said anything to the contrary.

You're crazy if you do FP without HKT though.

~~~
seanmcdirmid
Higher-kinded types can definitely be overused. One has to be careful with
higher-order anything, since it complicates reasoning about programs. (First-
order is simple if you can get away with it)

------
twotwotwo
There is one minor syntactical convenience available to make the message
example slightly more concise: switch msg := msg.(type) would make msg a
message of the specific type inside each case. (So msg would be a DrawLine
between 'case DrawLine:' and the next case.) Author might have just wanted
different names in the different cases, which is a perfectly defensible reason
for doing it the way he did instead.

He says "Go seems to advocate runtime over compile time checking." I think
it's less that folks _prefer_ run-time checks on principle than that they
simply don't yet have in hand great ways to strengthen compile-time safety
while meeting their other goals (meeting backcompat promises, not complicating
spec/impl too much, other work like shorter GC pauses or new platform
support). As an example of a team member not being a fan of all the runtime
checks, bradfitz complains about the occasional gross interface{} here:
[http://talks.golang.org/2014/gocon-
tokyo.slide#50](http://talks.golang.org/2014/gocon-tokyo.slide#50) . And there
is active work from the team and community on static checking, though most of
that right now is in the form of heuristic checks in external tools, e.g., go
vet and errcheck.

All in I love this--I tire a bit of posts that are like "I wrote a toy program
[or not even that]; here's Why Everyone Else Is Wrong" (because _anyone_ could
give an opinion that informed given a day or two, and hot takes are getting
old). But it's cool to see someone who, by virtue of working on Rust (and
seeing it evolve) has unusual perspective on tradeoffs and seems to have
written, you know, pretty thoughtfully and maturely about them.

~~~
Manishearth
That's pretty sweet, I'll remember that trick. The `range` trick was another
one that took me some time to realize, there are lots of neat sugary things in
Go like this :)

> I think it's less that folks prefer run-time checks on principle than that
> they simply don't yet have in hand great ways to strengthen compile-time
> safety while meeting their other goals

Agreed, that sentence was a bit unclear from me. I did mean that they're okay
with losing compile time safety over other benefits.

> All in I love this--I tire a bit of posts that are like "I wrote a toy
> program [or not even that]; here's Why Everyone Else Is Wrong" (because
> anyone could give an opinion that informed given a day or two, and hot takes
> are getting old). But it's cool to see someone who, by virtue of working on
> Rust (and seeing it evolve) has unusual perspective on tradeoffs and seems
> to have written, you know, pretty thoughtfully and maturely about them.

Aww, thanks :D

------
hedgehog
I've written a fair amount of Go and his view is pretty consistent with my
experience. Go has a good culture of doing things the simplest correct way and
only that way. Rust is really interesting because of the stronger compile-time
assurances and lighter footprint (looks like it'll be a good candidate for
embedding into mobile apps), I'm learning it in the hopes that I'll be able to
use it as an alternative to C for portable library code.

~~~
technological
Sorry for asking off topic question. What is good resource you suggest for
understanding go memory management. For someone I am unable to understand from
golang docs

~~~
Manishearth
The nice thing about Go is that you rarely have to think about memory
management. Can you access the object? Then it exists! (even across threads)

You should use locks for thread safety though.

That being said from a Rust background I like to be able to know what's going
on at the lower level and Go makes that harder, but that's not a major issue
whilst programming.

~~~
Manishearth
FWIW in Rust "Can you access the object? Then it exists!" is true too, however
you have to wait for the compiler to reach a certain point to know that you
indeed were allowed to access it[1], whereas in Go you can figure this out by
looking at the code :)

[1]Or internalize the borrow checker, which is something which eventually,
automatically happens to all Rust programmers AFAICT. It actually spills out
into other programming languages too so you get a nice involuntary mental tool
to ensure safety in C++ :)

------
rakoo
> Having interfaces be autoimplemented [...] it's just not what I'm used to
> and it restricts me from writing certain types of code without using dummy
> methods like so to get static type safety.

There's a Go idiom for that. If you have

    
    
      type Doer interface {
        Do()
      }
    

and a

    
    
      type thing struct {
      }
    

and you want to make sure that `thing`s implement `Doer`, just add this line:

    
    
      var _ Doer = thing{}
    

_ is a special value that can be redefined at will, so the pattern can be used
as many times as needed. From there, the meaning should be straightforward: _
is a `thing` that implements `Doer`, so the compiler will check it. You get to
keep implicit interfaces, that you can make explicit as you need.

Apart from that, this post is really great; as someone "used" to Go and not at
all to Rust this kind of impressions is always enlightening.

~~~
pcwalton
There's still no solution for the cowboy/shape problem (where you want to have
a single object that can implement Cowboy::draw() and Shape::draw()) though,
presumably.

~~~
doragcoder
You can use object composition:
[http://play.golang.org/p/jyS38luYjM](http://play.golang.org/p/jyS38luYjM)

Then make a Draw() method on the single object default to what you expect. The
simple inline explanation is:

    
    
        type SingleObject struct {
            Cowboy
            Shape
        }
    

Access is as follows to be unambiguous:

    
    
         Woody.Cowboy.Draw() 
         Woody.Shape.Draw()

~~~
TheDong
The problem is that you can't differentiate SingleObject from CowboyObject and
ShapeObject.

They all are the same interface and can be passed into each other's methods,
leading to a logical error.

------
rmcpherson
> Visibility (public/private) is done via the capitalization of the field,
> method, or type name. This sort of restriction doesn't hinder usability, but
> it's quite annoying.

While the capitalization of identifiers may be surprising, in practice it is
one of the best features of go in my experience because no additional context
is needed to know whether an identifier is exported. In rust, as far as I can
tell, it is not possible without referring to the declaration.

As far as globally changing whether an identifier is exported, it is easy with
the gorename tool, no search and replace necessary. Gorename also updates any
references to the identifier in other packages in $GOPATH.
[https://godoc.org/golang.org/x/tools/cmd/gorename](https://godoc.org/golang.org/x/tools/cmd/gorename)

~~~
solipsism
_no additional context is needed to know whether an identifier is exported_

Isn't this the same as the argument for Hungarian Notation? And isn't it a bit
of a slippery slope? Why not add an 'i' for integers, so that no additional
context is needed to know whether a variable is an integer. Etc.

There is _a lot_ of information about a particular identifier that it would be
nice to know at any given time, and in my opinion it's arbitrary to pick just
one and make it a language rule. A much better solution is for to use IDEs or
text editor plugins to visually show (using color, formatting, bits of UI)
that information in a user-configurable and context-dependent way.

~~~
aidos
In defence of hungarian notation, the way Joel tells it makes a lot of sense
[0]:

 _" Simonyi’s original concept for Hungarian notation was called, inside
Microsoft, Apps Hungarian, because it was used in the Applications Division,
to wit, Word and Excel. In Excel’s source code you see a lot of rw and col and
when you see those you know that they refer to rows and columns. Yep, they’re
both integers, but it never makes sense to assign between them. In Word, I'm
told, you see a lot of xl and xw, where xl means “horizontal coordinates
relative to the layout” and xw means “horizontal coordinates relative to the
window.” Both ints. Not interchangeable. In both apps you see a lot of cb
meaning “count of bytes.” Yep, it’s an int again, but you know so much more
about it just by looking at the variable name. It’s a count of bytes: a buffer
size. And if you see xl = cb, well, blow the Bad Code Whistle, that is
obviously wrong code, because even though xl and cb are both integers, it’s
completely crazy to set a horizontal offset in pixels to a count of bytes."_

It's actually a practice I now use myself in certain contexts.

[0]
[http://www.joelonsoftware.com/articles/Wrong.html](http://www.joelonsoftware.com/articles/Wrong.html)

~~~
nbouscal
This is so exactly the argument for a decent type system that seeing it used
to argue for a _naming convention_ is downright surreal.

~~~
aidos
Yes, a good type system would help with these issues, but that's not the only
way to crack this egg. I mostly work in Python, so it's not an option, but
even if it were, using types for this stuff is probably a pretty heavy
solution in some cases. Obviously, that depends on your type system, but for
most of them having to define new types for different types of array indexes
would bre _really_ clumsy.

As soon as you open the types basket you have quite a lot of overhead that you
didn't have before (it's a tradeoff, as ever). Not only that, but types won't
help you on the readability front - not without an IDE. Spolskey's argument is
that a naming convention can make the code look obviously wrong, and there's
nothing wrong with that.

Hungarian notation has its place. I've used it before, and I'll do it again.
Used correctly it can make the code safer and clearer. Types give you that
too, but a naming convention is as simple as it gets, and it's free!

~~~
nbouscal
Why do you need the code to look obviously wrong when you have a compiler
capable of _telling_ you that it's wrong? It doesn't require a full-blown IDE
to get fast feedback; I use a fairly lightweight vim config that gives me
type-checking every time I save a file.

I also disagree about overhead. Defining new types is an O(1) cost, and decent
modern languages should have type inference so that there isn't a recurring
annotation cost either. Even accounting for the cost, if the benefit is 10
units and the cost is 1 unit, saying only that “it's a tradeoff” is a bit
absurd. When there are tradeoffs, engineers don't throw up their hands and
flip a coin, they calculate the costs and benefits and use the best solution.
Types _easily_ win in that calculation.

I don't disagree with using notation to fill the gaps when you must work in a
weaker language, but we shouldn't pretend that it isn't a weakness.

~~~
aidos
I think maybe my comment sounded like I was advocating notation over types.
I'm not, and I agree with everything you're saying.

I stand by my assertion that there are situations where a little notation is a
good thing.

A better argument for notation is the readability of the code (which I place a
high value on). That's a place where the types don't help as much, but the
notation does.

------
zemo
pretty even-handed.

> The other day I had to make a bunch of fields public to satisfy Go's
> encoding package, and code needed to be updated everywhere with a manual
> find/replace (the same string was used in other contexts too so it couldn't
> be done automatically)

gofmt has syntax-level replacements. So you could do:

`gofmt -w -r 'unexportedName -> ExportedName' *.go`

or something similar, and it would do the correct thing, ignoring the string
matches where it appears in other contexts.

~~~
libria
This is why we need an IDE or Neovim + some epic plugins. Who's working on
one?

~~~
shazow
As far as vim goes, [https://github.com/fatih/vim-
go](https://github.com/fatih/vim-go) is the best all-in-one plugin. Really a
pleasure to use and easy to install. Should work with neovim too.

------
eridius
> _Despite the performance costs, having a GC at your disposal after using
> Rust for very long is quite liberating. For a while my internalized borrow
> checker would throw red flags on me tossing around data indiscriminately,
> but I learned to ignore it as far as Go code goes. I was able to quickly
> share state via pointers without worrying about safety, which was quite
> useful._

I haven't gotten very far into the article yet, but this paragraph sent up a
huge red flag for me. GC does not make the code safe. All it does is ensure
that values don't get destroyed while they're still referenced. But you still
run the risk of data races. If you're writing single-threaded code then this
is fine (because single-threaded code doesn't have data-races), but the
program being written here is a concurrent program.

Obviously you can write safe programs in this fashion. But you still have to
think about safety. The difference is the compiler doesn't verify safety for
you, which means you can write code fast without worrying about the compiler
yelling at you, but your program may have race conditions in it.

Anecdotally, a few years ago when I was active with Go, a program of mine
would segfault about once a _month_. It was a command-line utility that I ran
maybe a few times a day. The segfault was so sporadic that I could never
figure out what was going on. Then Go 1.1 introduced the data race detector.
Using that, I was able to discover that the Go runtime was actually spinning
up two goroutines when I thought it was only using one (servicing the stdout
and stderr of a child process), and the one buffer object I was writing to
turned out to be written to from two separate threads. This usually worked,
but on that rare occasion it would lose the race and overrun the buffer. This
is a bug that plagued me for half a year, and I'm really glad that the Go team
created that data race detector because I never would have figured out what
was going on without it. But it's a bug that simply cannot happen with Rust.

~~~
acqq
> Using that, I was able to discover that the Go runtime was actually spinning
> up two goroutines when I thought it was only using one (servicing the stdout
> and stderr of a child process), and the one buffer object I was writing to
> turned out to be written to from two separate threads

Wait, does it mean that go makes your code running in more threads without you
knowing? When can that happen? Sounds scary if true.

~~~
eridius
It means that there's nothing in the API that tells you whether the function
might spawn a goroutine. It has to document it.

I don't remember the precise details anymore, but my recollection is I was
spawning a child process and passing Writers to use for stdout and stderr
(looking at os.exec, I don't immediately see anything that matches my
recollection, so I'm wondering if maybe I was using a different package). I
expected that it would internally do something like select over stdout and
stderr in one goroutine, therefore making it safe for me to pass the same
buffer object for both (because I wanted to combine writes). But instead it
was spawning 2 goroutines, not 1. And there was no evidence in the API that
what I was doing was wrong, the only way to know was for the data race
detector to flag it.

------
barakm
Not bad, at least a fair look!

One thing that immediately set off my radar (as a Go programmer) is the
paintingLoop switch on type.

Yes, sometimes a switch on type is necessary. However, this is not one of
those cases.

You want to have a Triangle and Line with a Draw() method and a "Drawable"
interface... and then just call into it. The signature of the loop in question
then is paintingLoop(ch chan Drawable).

An abuse of naming, but a Quit.Draw() could actually quit, for example. This
is just offhand though.

In short, good article, but the Go examples are written with a Rust intuition.
Which, I mean, fair enough; I started writing Go with a C++ intuition, with
different questionable practices ;)

~~~
4ydx
Glad you pointed this out. Hopefully the original author will make a note of
that on his blog because when people run across it they will be given the
wrong impression about how this would be handled in Go.

I love how "opinionated" and slim Go's design is. The less variation in code,
the better, in my opinion. It might not be as "fun", but frankly I have a lot
more fun programming when I don't have to deal with esoteric solutions to
problems.

~~~
Manishearth
I'm not so fond of editing an already published post, but I'll ask bcoe if we
can/should do that and add some clarifications.

------
cpuguy83
The unused imports thing seems to be a common complaint, however I just use
go-imports, and imports are added/removed automatically when I save the file.

I never ever have to even touch imports, except sometimes when two different
packages have the same name.

~~~
ewillbefull
This doesn't help with unused variables, which comes up just as often when
doing the same thing (coding piecemeal, testing if it compiles, etc.)

~~~
Laremere

      var x int
      _ = x  
      

Mildly annoying, but it avoids unused variables in any code which compiles,
and is a helpful reminder of "I was going to do something with this value".

~~~
tatterdemalion
It seems like a warning only lint would be less annoying and a better
reminder.

~~~
sukilot
It is an established fact that humans cannot be trusted to handle warnings
responsibly.

~~~
anon1385
Judging from this thread Go users are regularly using various tricks and hacks
to avoid the compiler errors. So evidently Go programmers can't be trusted to
handle compiler errors responsibly and not use hacks to hide them. In which
case, what is the point of making in an error? It just makes things more
difficult for the responsible programmers who don't ignore warnings (instead
of a warning that they will notice they have to find and remove an obscure
line of code).

It was an interesting experiment to make these things errors only, but it's
clear that it has failed.

------
Manishearth
Note: this was originally posted at
[http://inpursuitoflaziness.blogspot.in/2015/02/thoughts-
of-r...](http://inpursuitoflaziness.blogspot.in/2015/02/thoughts-of-rustacean-
learning-go.html) , however I thought it would be nice to go through the
Polyglot Weekly editorial process and come up with a better version of it :)

------
BenjaminCoe
Having next to no Go experience, and zero Rust experience, I found this
article a really fun read. Coming from a Node.js background, it was neat to
see the concurrency approaches used in both languages compared -- quite
different from a single-event-loop :)

~~~
fizzbatter
I'm sure it just comes down to personal preference, but as a node developer
myself (perhaps former), i found it so freeing to switch to Go. Mainly for two
reasons (which i assume are less common reasons):

1\. Synchronous by default 2\. Interfaces

Point 1 to me feels silly to admit. But man, i still do a fair bit of
JavaScript/Node for work (our frontend, mainly) and it is just so.. tiresome
to me, to constantly feel on the edge of bugs because some functions are
expected to be synchronous (return values) and others are intended to
callback.

I'm not even talking "nested-hell", i'm simply referring to the mental
overhead that i apparently use when using any JavaScript function. It's not
difficult.. it's just not enjoyable. It's.. tiresome for me.

Point 2 is simply because i really like interfaces. Back from my Python days,
a few libraries had invented ways to support interfaces and i really really
liked them, but not the overhead they required. Being able to expect the
behavior of an object with certainty is the main part of duck-typing to me,
and seeing that from a more static language (when compared to python/js) is
really enjoyable.

I know you didn't ask for a writeup, i just had to agree with you and felt the
need to share my experience from a similar standpoint. :)

~~~
kuschku
But that’s the issue, duck typing really leads to many situations with
uncertain situations. It’s like the + operator in JS: depending on context,
you can have appending, addition, or vector addition.

~~~
fizzbatter
Are you referring to duck typing in general? Or specifically Go's interfaces
(which i referred to as duck typing)?

They seem rather harmless to me, so i'm curious on your perspective. I see
them as no more dangerous than an explicit type (or any function, for that
matter).

You're asking for a specific behavior, and you _know_ that what you get will
have that specific behavior. A Reader or Writer is a simple example of that.
Sure, you don't know what it's writing to, but that's outside your scope and
likely the scope of the function - right?

Perhaps i misunderstand you, i'd love further explanation/examples
_(pertaining to Go's interfaces)_ :)

~~~
kuschku
Imagine I get an object, I don’t know it’s type – I can’t even check if it
implements the VectorAddition, Addition, or Appendable trait, I only see it
has a .+ method.

How am I supposed to switch between types being able to do either of these,
without listing all types that might occur – as this might not be possible

(I’m thinking about Haskell’s type classes as a solution to this, btw)

~~~
vectorjohn
Plus is a bad example because you can't do that in Go, but I understand the
question.

Basically, you don't do that in Go. Go has interfaces, but they're implemented
implicitly. So rather than "I get an object", you say, "I get an object that
implements interface X", and you use whatever methods X has.

That just means its up to you to name interfaces in a reasonable way. Which is
one down side - if you have an "Add" method, it's back to your question about
what it means to add, depending on the implementation.

Nothing does this as well as Haskell as far as I can tell.

------
amelius
My biggest problem with Go is this one [1]

In short, assign nil to a variable, and it ends up not being equal to nil
(?!?) Really, how does something like this get approved?

[1]
[https://golang.org/doc/faq#nil_error](https://golang.org/doc/faq#nil_error)

~~~
NateDad
A nil pointer is a perfectly valid value that can satisfy interfaces.

[http://npf.io/2014/05/intro-to-go-
interfaces/#toc_4](http://npf.io/2014/05/intro-to-go-interfaces/#toc_4)

------
vanderZwan
> _Eventually I was able to learn the proper style by watching my code get
> corrected._

Funny thing: I actually benefit from _not_ typing idiomatically! GoSublime
runs gofmt whenever you save a file. It's a free check for syntax errors, as
it won't fix the formatting if you made a mistake. And because the mistake
must be in the non-idiomatic formatting, it's easier to spot too.

> _I 'd love to see a rustfmt, however!_

Seriously, I miss gofmt in any language that doesn't have a similar feature.
I'm so surprised it hasn't been copied more!

~~~
euyyn
Reminded me of the old Visual Basic, in which you'd declare your variables
with caps but write their usage in lowercaps. This way the instantaneous auto-
capitalization checked for you that you had written the name correctly
(instead of implicitly defining a new variable!).

~~~
ReidZB
Even today, auto capitalization fixing (and just auto-formatting code as you
type in general) is one of my favorite features of Visual Studio, one that I
wish I could have in every IDE/editor.

For example, if your class is named FooBar and you are declaring a FooBar
variable by typing foobar, Visual Studio will just auto-fixit as you type.
Same goes for properly formatting if statements, declaring functions required
by interfaces, etc... In comparison, Xcode requires you to "confirm" the
autocompletion fix by hitting enter, which is less ergonomic in my opinion.

------
ryderm
Interesting point of view to hear. RE: enums - There is an idiomatic way to do
enums in go I believe:

    
    
      type MyEnum int
    
      const (
            A MyEnum = iota
            B
            C
            D
      )

~~~
Manishearth
Rust's enums aren't like Java enums, they are algebraic datatypes and each
variant can hold different data. Check out the example with DrawTriangle and
whatnot for what I mean :)

~~~
eternalban
You are not correct regarding Java's enums. Define properties for your enum
type, widen the constructor appropriately, and provide the accessor methods:

[https://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html...](https://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.9)

~~~
nightpool
That's not the same thing. Rust enums are types, each with the possibility of
having different fields that store data. Java enums are instances of a type,
each with static data.

(rust enums can have associated data too, but its not their main usage in
idiomatic code)

------
d9k
What about compilation speed of Go/Rust? Surprisingly there wasn't mention
about this in article. I consider slow compilation as a main issue of Rust
compared to Go. Am I right?

~~~
Manishearth
Rust is very slow, yes.

However, I was writing a rather tiny library so it wasn't a major factor, so I
didn't notice this until much later.

I did intend to add a blurb about this when posting to Polyglot Weekly, but I
.. forgot ;P

~~~
d9k
thanks for answer)

------
Animats
Having written in both Rust and Go, I found that a very good article.

The big new idea in Rust, of course, is the ownership model and its compile
time borrow checker. That's worked out surprisingly well. I expect to see that
idea appear in future languages. One could have a Go-like or Python-like
language with a borrow checker. It's not inherently tied to Rust.

As a practical matter, programming in Rust is more likely to hit problems with
type checking than borrow checking. Ownership problems are usually with your
own code. Type problems often involve code written by others, and changes to
other modules often induce type errors. Because Rust has type inference, and
because it's often not obvious what the type inference engine is doing, Rust
can have rather abstruse type problems. Go is rather straightforward in that
area.

Go lacks generics and templates. This simplifies the language, but the lack of
generics then has to be worked around. Go has too much use of "interface{}".
Bolt-on compiler passes such as "go generate" and the protocol buffer compiler
have been added. Both are used mostly to get around the lack of generics. Rust
has very powerful generics and templates, so powerful that they tend to be
over-used. Everything seems to need a generic. Rust is starting at the cruft
level it took C++ 20 years to reach. This may or may not work out in practice.
It may be necessary to restrain Rust template enthusiasts from getting carried
away.

Error handling in both languages is via return values. Go is straightforward,
with multiple return values being the normal approach. The Rust mechanism,
involving Result<> and Some<> types, is rather clever, but results in wordy
code to do simple things. Too much Rust code goes into putting things into
Result and Some objects, then analyzing and dismantling those objects. It's
not clear that leaving exceptions out of Rust is a win. Negative experience
with C++ exceptions may have motivated that design decision.

Rust has Resource Allocation Is Initialization (RAII). Go does not. For both
languages, problems come up with resource release. Go has "defer()", which is
kind of clunky and can't be encapsulated, while Rust has destructors called
when something is deallocated. In both cases, it's hard to handle an error at
resource release time. Destructors that do anything non-trivial are usually
troublesome. Both languages have this problem. Languages with "with" (Common
LISP's "(with-open-file ... ...)" and Python's "with FILEOBJ as HANDLE : "
have fewer headaches with resource release errors, since they occur in
ordinary code, not at some special destructor time.

~~~
deckiedan
In rust, can't you just use a {} block to force the scope limitation?

(I've just browsed the rust docs, never written anythign in it)

    
    
        fn do_stuff() {
          // already inside a function!
          {
            // allocate here...
            let x = Giraffe();
          }
          // should be deallocated by now. right?
        }

~~~
Animats
You can certainly limit scope in Rust. That's not the problem, though. The
problem is, what if something goes wrong in Giraffe's destructor? Like maybe
the remote procedure call to Zoo fails.

------
infogulch
goimports-on-save is an _essential_ tool in your writing environment for go.
It's not mentioned in the official docs, but using it completely obviates
fiddling with your import declarations ever again. While you're at it, you
might as well turn go vet-on-save and go lint-on-save as well.

~~~
Manishearth
Yeah, it's been mentioned to me in this thread many times :) I so wish I had
known beforehand ;p

It would be nice to have a cheatsheet full of Go tips & tricks like
gofmt/goreplace for replacing idents, and goimports-on-save, and whatnot. TBH
I didn't really look for such non-in-built solutions when starting though, so
I would have missed it anyway :P

~~~
infogulch
> I didn't really look for such non-in-built solutions when starting

I can't fault new users for this; the non-discoverability of these awesome
tools for new users is a genuine fault of the Go ecosystem.

Just a "Tips" or "Useful Tools" list at the end of the Getting Started page or
the How to Write Go Code page would be better. Just to make users aware that
such tools exist.

------
BuckRogers
"Unlike Rust, Go is really easy to pick up. It's possible to jump directly
into it, and you can be writing useful programs after a single afternoon of
messing around or reading."

I think it's safely implied that you put more effort into Rust code, but get
more out of it. It's a tradeoff from Go. It's worth it for some programs, and
others it's not.

Go for me doesn't work out because it's not C ABI compliant which is a
limitation Rust doesn't have. As a language nothing draws me to it in
particular. The killer feature is how easy it is to get started with it.
Something that is undersold.

Coming primarily from Python I'm still favoring the field over Go until my
problems matched Google's. I would probably reach for Erlang for concurrency
and Rust for anything low-level going forward.

------
_pmf_
The most important thing I took away was that Rust has enums as tagged union
types; I cannot count the number of times I wished I had this in my day-to-day
language.

------
jerf
"Unlike Rust, Go is _really_ easy to pick up. It's possible to jump directly
into it, and you can be writing useful programs after a single afternoon of
messing around or reading. On the other hand, while basic Rust is easy to pick
up, it takes a while to get used to the borrow checker (and in general
understand ownership/borrowing). Additionally, most libraries make full use of
advanced features (like associated types) and one needs to learn these too to
be able to use the libraries."

This is pretty much why I consider these two to be in less competition than
first meets the eye. Rust intrinsically involves putting more effort in to the
code. You put more effort in and you get much more out, but there's a lot of
code for which that's not the right tradeoff, just as there's a lot of code
for which it is.

"In Go there's no obvious way to get this. The closest thing is the type
called interface {} which is similar to Box<Any> in Rust or Object in Java....
Of course, I could implement a custom interface MyMessage on the various
types, but this will behave exactly like interface{} (implemented on all
types) unless I add a dummy method to it, which seems hackish."

I think most Go programmers stick to interface{}, but personally I put the tag
method on the type, being from the Haskell side of the house on that front. In
practice it turns out that often the "tag" interface grows a useful method of
some sort, at which point it ceases to feel silly. If nothing else, you've got
the ".Handle()" option. See also
[http://www.jerf.org/iri/post/2917](http://www.jerf.org/iri/post/2917) .

Also, just generally on the performance front... developers who care about
performance are at this point subconsciously pretty used to being able to
choose between "language like Rust or C++ that really, _really_ cares about
performance, but is hard to use" and "dynamicly-typed language that's really
flexible and powerful but 50-100 times slower than the other choice". In the
limit, Rust will absolutely be faster than Go, no question, but the
performance penalty may be more like a factor of 2 in the end... Go's pretty
fast, and while you can't go absolutely crazy with optimization C-style, you
do get to play with locality optimizations with real structs and real arrays
and such. Worrying about the performance differences between C and Python
before you write a line of code may be perfectly valid, because the gulf is
pretty large... worrying about Go vs. Rust is going to be a much narrower band
of validity before you're just prematurely optimizing. Rather a lot of high
performance code still has "vtables" in it, used quite extensively.

In practice, I don't expect many people to decide between Go and Rust based on
performance needs on similar code... non-zero, yes, but not many. What expect
people to choose between them are general software engineering complexity
considerations. When it comes down to it, the reason to write Servo in Rust
and why Rust needed to be created to do it isn't that there weren't languages
that were "fast enough"... it's that the browser has bat-shit insane sharing
patterns, complexity beyond the mind of any mere mortal, and you end up with
code that either does way too much copying or crashes when it doesn't do
enough, because there's no ownership management in the language. That's the
sort of reason to choose Rust over Go. If you're making something that looks
like a browser, or an office suite, or a big highly-integrated business
workflow suite, or something like that, I'd choose Rust over Go in a heartbeat
simply because Rust is designed to shine in that environment, and Go
ultimately doesn't particularly offer anything to make that scale of that sort
of code any better than any other statically-typed language.

~~~
Manishearth
Fun fact: The professor who asked us to use Go in the course also knew Rust
and Nim and in general liked programming languages. When I shared the original
version of this article with him, he said this interesting tidbit (which I
agree with wholeheartedly):

> ... there is no other language I can think of with such a small surface area
> that I can tell students in the first week to learn and deliver an
> assignment by the second week

...

> This is pretty much why I consider these two to be in less competition than
> first meets the eye. Rust intrinsically involves putting more effort in to
> the code. You put more effort in and you get much more out, but there's a
> lot of code for which that's not the right tradeoff, just as there's a lot
> of code for which it is.

I don't consider the two to be in competition (aside from "which hip new
language should I learn today") either, though it's less due to the easy-to-
pick-up thing and the Rust mentality that:

\- If it can be a footgun, be explicit (unsafe, exhaustive matching, unwrap)

\- If you can do it at compile time, DO IT DO IT DO IT DO IT (all the
compiletime type safety, ownership, etc). All abstractions should be zero-cost
or with minimal cost (at runtime). Try to write your code so that tests won't
be necessary (because a successful compile should _be_ a test), and then write
tests anyway!

(This mentality is pretty prevalent in both the Rust stdlib/language and
external libraries. The equivalent of interface types in Rust (`Box<Trait>`)
is generally frowned upon and only brought out when absolutely necessary, for
example)

Go doesn't really embrace either of these principles, so it doesn't compete
that way. You're right, developers already are used to making those choices.

> worrying about Go vs. Rust is going to be a much narrower band of validity
> before you're just prematurely optimizing. Rather a lot of high performance
> code still has "vtables" in it, used quite extensively.

Not necessarily. As mentioned before, the Rust way of doing things generally
leads to a _lot_ of optimized stuff. Taking the vtable example -- Go code that
I've seen uses vtabley things _everywhere_ (the stdlib is peppered with
interface types). Using vtables isn't bad, but when you use it for stuff that
could have been solved by static dispatch (for large codebases -- that can be
_everywhere_ ), then it can become an issue. In Rust a vtable is a last-resort
that people will grumble about before picking up and using (but will use it
whenever absolutely needed). Sure, a single vtable isn't much of a performance
optimization, but when all your APIs use static dispatch due to this
mentality, the collective optimization is huuuge.

> In practice, I don't expect many people to decide between Go and Rust based
> on performance needs on similar code... non-zero, yes, but not many.

For many applications, I agree. Systems programming is one where this doesn't
apply, but for stuff like a distributed consensus algorithm implementation
(which I was doing), both Go and Rust are perfect. And I'm pretty sure that if
I'd had more practice in Go, I would have been confused which language to
write it in. (Right now I love Rust and am not great at Go so of course I
would use Rust if given a choice)

For stuff like a browser, it's not so much that there are batshit insane
sharing patterns, it's more because a browser is huge and performance matters
a LOT. (I think pcwalton above explains why better than I can)

~~~
jerf
"Not necessarily. As mentioned before, the Rust way of doing things generally
leads to a lot of optimized stuff. Taking the vtable example -- Go code that
I've seen uses vtabley things everywhere (the stdlib is peppered with
interface types). Using vtables isn't bad, but when you use it for stuff that
could have been solved by static dispatch (for large codebases -- that can be
everywhere), then it can become an issue."

Yes, but by definition, we're discussing optimized code here.

[http://benchmarksgame.alioth.debian.org/u64q/benchmark.php?t...](http://benchmarksgame.alioth.debian.org/u64q/benchmark.php?test=all&lang=go&lang2=rust&data=u64q)

Yeah yeah, benchmarks blah blah, but the point here is, compare with, oh, say,
this:

[http://benchmarksgame.alioth.debian.org/u64q/benchmark.php?t...](http://benchmarksgame.alioth.debian.org/u64q/benchmark.php?test=all&lang=perl&lang2=rust&data=u64q)

As I said and will repeat without blinking, Rust will always be faster than Go
(and the problem in the shootout Go beat Rust on is in my opinion certainly
simply an unoptimized Rust implementation, no question), but language deltas
of 2x or 3x in practice are dominated by algorithms and IO far more often than
50-100x. For all the long list of reasons that Rust is faster than Go, all of
which are true, they add up to much less than _developers are used to_.
Intuitively, when you hear "go is slower than Rust because vtables and bad
optimization and GC and its pauses and interfaces" you probably mentally
expect Go to be like 10 times slower, when instead it's 2-3.

This is relevant because I don't think Rust advocates should sell "speed!" too
hard as the primary thing they have... C++ will pretty much forever be able to
keep up on the raw speed front, especially in benchmarks. What it has is
_complexity management, at speed_. But complexity management first.... Rust
could probably take a straight-up 2x speed penalty over C++ and _still_ write
a renderer than ran faster than C++, because it'll be a _better_ renderer.

~~~
igouy
> unoptimized Rust implementation

 _Scotty! We need more CORE[s] ! "_

------
lordbusiness
My hat is off to the author of this piece; what a beautifully written fair
evaluation of two technologies.

Kudos.

------
JulianMorrison
Here's part of why the interface thing is useful: it gives you dependency
injection almost for free, and it allows modules to be extremely decoupled.
You can create a module-local interface describing not just a "generic X" but
"specifically what methods of X I want". That way you don't even depend on
other object's types, but only on their permitted actions. And making mocks is
very easy. The IO libraries use this extensively; they don't depend on where
you're getting your IO from. They just require it does this or that.

Bonus, you can define "tolerable minimum" and "nice to have" versions of the
same local interface. Define your methods in terms of the first, but attempt
to cast to the second.

    
    
      if nice, ok := minimal.(Nice); ok {
        // do stuff only possible with the nice to have interface
      }
    

Example: the HTTP libraries will flush your IO if you give them something
flushable, but they don't require it.

------
smegel
It seems he approached this in good faith and gave an honest review. Which
seems quite rare for people commenting on Go.

------
Xeoncross
I might be mistaken, but `func bar(){}` is not public, I think you meant to
capitalise the "B".

~~~
Manishearth
fixed, thanks!

------
sgt
My biggest gripe with Go: Unused variables. Unused anything doesn't let me go
on with coding. It's obviously a design decision from the makers of Go, but
the way I usually program is "experiment first, then clean up".

~~~
Manishearth
We used to have `#![deny(unused)]` in Servo, and it was quite annoying.
Especially because a compile takes a while and the lints run mid-compile
(after type/borrow check but before the llvm stuff). Quite annoying,
eventually we turned it off. But it wasn't _so_ bad; because when writing Rust
code there's more fixing of type/borrow errors and less waiting for a full
compile and running tests; since most of the "correctness" is encoded in the
types used.

------
Nitramp
Regarding enum support, you can create type safe enums in Go like this:

    
    
        type Color int
    
        const (
          Red     = iota
          Blue
          Green
          Sausage
          Yellow
        )
    

The `Color` type is not compatible with int, so except for explicit casts and
literal assignments, it's type safe.

Though that's quite a different enum style compared to the tagged unions of
Rust, his approximation with interfaces is indeed closer to that.

~~~
ibotty
but that's only a simple c-style enum. What the author is describing is a
algebraic data type/sum type: an alternative between types with possibly
different types (i.e., a Boolean, two strings, etc.). Read the example in the
article closely and you will know what I mean.

------
kylequest
Looks like an error in the Go code in the What I didn't like/No enums section.
In the paintingLoop function the type assertion switch should be on the
message and not the channel.

Should be "switch msg.(type)" instead of "switch ch.(type)"

~~~
Manishearth
thanks; fixed!

------
orbitur
Re: Go errors for unused imports

> This rules out some workflows where I partially finish the code and check if
> it compiles.

Sounds like a great job for an IDE to automatically add/remove the appropriate
imports for standard library stuff.

~~~
fizzbatter
Goimport (i believe is the tool name, i use it for Vim) does just that. Also,
because of the namespacing in Go, it actually works for non-standard library
imports as well. It runs along with Gofmt on every save for me.

The only time in my experience that it has trouble is when there is a
namespace conflict and it has to guess. Generally that's easy to avoid though,
and uncommon.

------
1_player
The problem with Go is that you don't grok Go interfaces.

EDIT: downvoters, please explain what's wrong with my comment.

Go interfaces are quite a novel concept, and most developers that jump from
language X to Go don't have enough time and experience to understand how they
work, and how to design their application around this new model.

Their example of the messaging system/UI application is a straight port of the
Rust code, and they lament the fact they have to use interface{} to overcome
the lack of object orientation or pattern matching.

    
    
        func paintingLoop(ch chan interface{}) {
            // shorthand for a loop over receiving over
            // a channel
            for msg := range ch {
                switch ch.(type) {
                    case Quit:
                        // quit
                    case DrawLine:
                        // cast to a drawline message
                        line := msg.(DrawLine)
                        // draw `line`
                    case DrawTriangle:
                        tri := msg.(DrawTriangle)
                        // ...
                    case SetBackground:
                        bg := msg.(SetBackground)
                        // ...
                    default:
                        // Need a default case in case
                        // some other type is fed through here.
                }
            }
        }
    

This is not idiomatic Go. The author here should be leveraging Go interfaces
and they wouldn't lose type safety.

    
    
        type Drawable interface {
            Draw()
        }
    
        type Line struct {
            x1, x2, y1, y2 int
        }
        func (l *Line) Draw() { ... }
    
        type Circle struct {
            cx, cy, radius int
        }
        func (c *Circle) Draw() { ... }
    
        func paintingLoop(ch chan Drawable) {
            for object := range ch {
                object.Draw()
            }
        }
    
        func main() {
            ch := make(chan Drawable)
            go paintingLoop(ch)
    
            ch <- &Line{x1: 10, y1: 10, x2: 50, y2: 50}
            ch <- &Circle{cx: 10, cy: 10, radius: 20}
        }
    
    

You wouldn't expect Python programmers to try Haskell over a weekend and have
enough experience with it to write a blog post about "Thinks I dislike about
Haskell", but it seems much to often people play with Go and, when they fail
to translate their ideas to this new language, post yet another "rant" on HN
about how Go sucks.

Go has its fair share of shortcomings, but try to _learn_ the language before
writing yet another misinformed article.

~~~
masklinn
> Go interfaces are quite a novel concept

They're not: [https://realworldocaml.org/v1/en/html/objects.html#ocaml-
obj...](https://realworldocaml.org/v1/en/html/objects.html#ocaml-objects)

> This is not idiomatic Go. The author here should be leveraging Go interfaces
> and they wouldn't lose type safety.

You've conveniently ignored the case of the non-drawing commands. Should they
be Drawables with a Draw which does not draw anything?

> Go has its fair share of shortcomings, but try to _learn_ the language
> before writing yet another misinformed article.

The OP has been upfront about his limited knowledge of Go, and his rust
knowledge coloring his thinking.

~~~
pcwalton
> You've conveniently ignored the case of the non-drawing commands. Should
> they be Drawables with a Draw which does not draw anything?

In particular, you can't implement Quit using a virtual method, unless you do
something like have every function return a boolean to the caller indicating
whether it wants to quit (or panic and recover, but that's too awkward for
words).

You also can't have methods that set local variables in the caller, unless you
do something like pass in their addresses to the function, which gets awkward
quickly if you have a lot of local variables.

~~~
azth
> You also can't have methods that set local variables in the caller...

How would you do that using ADTs?

~~~
pcwalton
You don't use methods, but instead switch on the discriminant of the ADT and
take different actions (such as updating local variables).

This is pretty related to the so-called expression problem. OO-style methods
are good at extensibility, but they're bad at adding extension points (method
calls), since you have to update every one and calls are limited in expressive
power compared to the body of a switch statement. Functional-style ADTs are
bad at extensibility (adding a new type requires updating all pattern
matches), but they're good at adding new extension points, since you don't
have to touch the data types and pattern matching is powerful. Supporting both
styles in a language gives programmers the freedom to choose the best one for
the task at hand.

~~~
azth
Makes sense. Thanks.

