17 ms·
A Rust Contributor Tries Their Hand at Go
- Manishearth 12y agoNote: this was originally posted at http://inpursuitoflaziness.blogspot.in/2015/02/thoughts-of-rustacean-learning-go.html http://inpursuitoflaziness.blogspot.in/2015/02/thoughts-of-r... , 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 12y agoHaving 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 12y agoI'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. :)
- agmcleod 12y ago> 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. Pretty much why i never got into node development.
- BenjaminCoe 12y agoI love the Node/npm community, and have been programming JavaScript for so many years it's hard habit to kick :) I'd love to try my hand at Go though... I think it's awesome to see what practices can be shared between communities. One approach I've been playing with in JavaScript-land is using Promises for all return values, this means that you consistently know how to interact with a return value, whether its concrete-value is available immediately or at a later time -- this doesn't really solve the problem of consuming other people's libraries however!
- kuschku 12y agoBut 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 12y agoAre 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 12y agoImagine 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)
- hedgehog 12y agoI'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 12y agoSorry 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
- rmcpherson 12y agoThe best place to start, if you haven't read it, is the documentation here: https://golang.org/ref/mem https://golang.org/ref/mem For more detailed information on the garbage collector improvements and roadmap for 1.5 and beyond, this document is the Roadmap and discusses improvements being made including a maximum of 10 ms STW out of every 50 ms. https://docs.google.com/document/d/16Y4IsnNRCN43Mx0NZc5YXZLovrHvvLhK_h0KN8woTO4/preview?sle=true https://docs.google.com/document/d/16Y4IsnNRCN43Mx0NZc5YXZLo... edit: fixed incorrect link
- hedgehog 12y agoIn addition you can get some insight for the "why" questions by reading through their mailing lists (dev and nuts).
- Manishearth 12y agoThe 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.
- ryderm 12y agoInteresting 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 )
- nemothekid 12y agoRust's Enum, it looks like, aren't the same as C-esque enums, and look a lot closer to Go-interfaces (or like the article says C's unions). That said I wonder why they named it enum.
- cygx 12y agoTagged unions in particular, C's version of variant types. An enum can be considered a degenerate case of such a type, so the naming isn't totally arbitrary.
- masklinn 12y ago> That said I wonder why they named it enum. Because it's a type-safe enumeration? ADTs are pretty much a superset of C-style enums, an ADT with all-dataless constructors is equivalent to a C enum: enum Foo { Bar, Baz, Qux, Quux } And members can converted to integrals: > println!("{}", Foo::Qux as u8); 2 With explicit integrals very much like C: > enum Foo { Bar = 5, Baz, Qux, Quux } > println!("{}", Foo::Qux as u8); 7 Calling it "enum" makes it familiar to C developers[0], "union" would have been confusing for the same (as you can't just write one representation and read the other) and "type" is a tad too generic (and used for type aliases à la typedef). [0] even more so C++ developers as they have `enum class` which is also type-safe and not implicitly converted to integrals
- nemothekid 12y agoWell you are right, the name does make sense, but I would have thought "union" would have made more sense than "enum" for those of us from the C-family. OTOH I haven't written a line of Rust of yet, so enum might be more familiar in use.
- zemo 12y agopretty 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.
- Manishearth 12y agoThanks! I wish I'd known this a week ago, but I'll keep it in mind for the next time I'm poking at Go :)
- johncch 12y agoI use gorename http://gopkgs.com/doc/pkg/gopkgs.com/gc/go.tools/cmd/gorename http://gopkgs.com/doc/pkg/gopkgs.com/gc/go.tools/cmd/gorenam... and it works well with vim-go integration on vim for example
- blaenk 12y agoWe should really think about getting something like this in rust/rustfmt. Syntax-level replacements sound pretty awesome.
- libria 12y agoThis is why we need an IDE or Neovim + some epic plugins. Who's working on one?
- cpuguy83 12y agoThe 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 12y agoThis 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 12y agovar 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 12y agoIt seems like a warning only lint would be less annoying and a better reminder.
- sukilot 12y agoIt is an established fact that humans cannot be trusted to handle warnings responsibly.
- anon1385 12y agoJudging 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.
- jerf 12y ago"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.
- Xeoncross 12y agoI might be mistaken, but `func bar(){}` is not public, I think you meant to capitalise the "B".
- Manishearth 12y agofixed, thanks!
- barakm 12y agoNot 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 ;)
- BenjaminCoe 12y agoI'd love to post a Go-best-practices article on Polyglot Weekly, as a rebuttal. If you or anyone else reading this thread would be interested :)
- Manishearth 12y agoYeah, that would work, though then I need to worry about all the local variables too (which I didn't use in the example but did need in the actual code). Still, not a major overhead since I can use a context object for that. Anyway, my issue was with the vtable dispatch of interfaces more than with type switches -- using a .Draw method is pretty much the same :) But yes. It was written with Rust intuition, and whilst I ried to learn the Go way of doing things, I obviously didn't pick up everything :P Thanks though; I'll try to use this pattern in the future!
- unoti 12y agoWould it be reasonable to create a separate channel for each message type, and switch over all of them in the receiver?
- Manishearth 12y agothat sounds heavy to me. Generally using multiple channels and select is good when you have multiple threads sending. When there is a single sender, putting everything in one channel feels better to me.
- rmcpherson 12y ago> 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 12y agono 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.
- nordsieck 12y agoIt's not the same. In Hungarian Notation, the type is enforced by a declaration, but it's documented by the prefix. In go, the enforcement and documentation are one and the same.
- aidos 12y agoIn 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
- infogulch 12y agogoimports-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 12y agoYeah, 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 12y ago> 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.
- 1_player 12y agoThe 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.
- zalmoxes 12y agoYour example is not idiomatic Go either, the interface should be named Drawer not Drawable.
- 12y ago
- zuzun 12y agoI 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.
- picardo 12y agoYou're exactly right. It's sad that a lot of people in the community cannot keep an open mind when they evaluate a technology.
- wyager 12y ago>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.
- bigdubs 12y agoCan you add more color here? What is the separation between generics and functional programming? As an example in favor of their coupling it is very hard to write generalized functions for list operations without generics (without duplicating code). It is especially hard if you want to maintain type safety and don't resort to type erasure (interface{}).
- rakoo 12y ago> 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.
- Manishearth 12y agooo, that's an interesting pattern :)
- pcwalton 12y agoThere'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.
- infogulch 12y agoThe solution is to use more specific names. E.g. Cowboy.DrawGun(), Shape.DrawTo(canvas) Yes, sometimes this isn't an option when you want to satisfy the interface for two different libraries, but I've not seen this occur very often. More likely you're in control of one of the interfaces; so rename your method to be more descriptive.
- pcwalton 12y ago> Yes, sometimes this isn't an option when you want to satisfy the interface for two different libraries This is assumed in the description of the problem. Go has no solution for it. (Java doesn't either, but this is one of the things that C# got right over Java; it's unfortunate that Go has the same issue.)
- vanderZwan 12y ago> 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 12y agoReminded 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 12y agoEven 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.
- dorfsmay 12y agoTotally agree. When I started writing python on my own, I dislike some of the PEP8, I liked two spaces, not 4, etc... But then as soon as I started working with teams, my personal preferences no longer mattered, sticking to PEP8 make made life way easier. I think eventually we'll just store the AST in our version control systems, and use interfaces that we will adjust to our preferences when editing, diff'ing etc...
- twotwotwo 12y agoThere 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 12y agoThat'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
- sgt 12y agoMy 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 12y agoWe 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.
- falcolas 12y agoThere's a way to work around that, a bit of a hack but it can help when quickly iterating: _ = unusedVar
- kylequest 12y agoLooks 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 12y agothanks; fixed!
- orbitur 12y agoRe: 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 12y agoGoimport (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.
- wukerplank 12y agoAtom and the Go Plus Package do exactly that. Very convenient!
- lordbusiness 12y agoMy hat is off to the author of this piece; what a beautifully written fair evaluation of two technologies. Kudos.
- smegel 12y agoIt seems he approached this in good faith and gave an honest review. Which seems quite rare for people commenting on Go.
- amelius 12y agoMy 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
- Animats 12y agoHaving 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 12y agoIn 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? }
- eridius 12y ago> 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.
- Manishearth 12y agoI was focusing on single threaded safety really. I do know that data races can't be prevented by a GC, but having to only worry about data races and not the regular memory safety bugs was quite nice. <3 Go's race detector (also the deadlock debugging support). Used it quite often though I didn't hit it much. Agreed, none of this is a problem with Rust :)
- acqq 12y ago> 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.
- d9k 12y agoWhat 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 12y agoRust 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 12y agothanks for answer)
- _pmf_ 12y agoThe 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.
- deleted 12y ago[deleted]
- BuckRogers 12y ago"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.
- JulianMorrison 12y agoHere'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.
- Nitramp 12y agoRegarding 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 12y agobut 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.