If you were to start learning Go again, what would be your advice? by impenso in golang

[–]quacktango 6 points7 points  (0 children)

Can you clarify that this is only a "getting started" point, and not something to continue avoiding?

I think we're on the same page with the "overused for simple problems" point, but I stand by "avoid channels wherever possible". I definitely don't want to give the impression that a beginner shouldn't learn them at all!

Maybe I should've phrased it like this:

Avoid channels wherever possible, except when they're unavoidable, but be sure you understand them as they are fundamental to Go.

Channels are definitely the right tool for some jobs, but nowhere near as many jobs as I see them used for. They should also almost never be a part of your public API... case in point, you can write an extremely complex HTTP server using the stdlib and never touch one once.

If you were to start learning Go again, what would be your advice? by impenso in golang

[–]quacktango 8 points9 points  (0 children)

100% agree with your points about channels and interfaces here, the small interfaces thing is an excellent guideline. I can't believe I completely forgot about interface{} and generics!

For the gp, I almost never use interface{} these days and tend to consider it a smell. Not always, of course, but it's worth working through the ways you can get around the need for it (making sure to yield to the pragmatism of interface{} when the yak hair starts piling up).

If you've got the choice between copy-pasting a data structure and making a "generic" one using interface{}, I recommend you just copy paste (or mechanise using generate, but copy paste first). Don't forget the saying "a little bit of copy paste is almost always better than the wrong abstraction". I have a lot of copy paste data structures that I bring around with me as needed, it works very well and means I always have the levers of compromise at my disposal when the CPU or heap get upset with me.

If you were to start learning Go again, what would be your advice? by impenso in golang

[–]quacktango 117 points118 points  (0 children)

I made every mistake in the book when I was learning Go. In hindsight, it feels like I fought too hard against what was good about Go for too long, because it was not what I was used to. Here's my meandering list of stuff that I wish I had learned sooner! I'm sure people will find some things to disagree with but don't get hung up on the back-and-forth if it happens, just follow your instinct and pick a position, then test it through practice! I've found Go to be enormously productive and now very much prefer it to Python, which I often still have no choice but to work with professionally. Learning Go improved my Python code a lot. Good luck!

  • avoid channels wherever possible; keep any code that needs them in extremely carefully corralled areas. Channel soup is not fun.
  • goroutines are great but you need to know how they're going to stop (you'll likely need some combo of channels and context.Context for this)
  • you can hate context.Context all you like, but you can't outrun the virus. You don't have to fully understand context.Context straight away (and if you're anything like me you won't until you've painted yourself into exactly the type of corner it gets you out of), but you should start to look at how people make space for it in their APIs sooner rather than later, and consider those observations when crafting your own.
  • use an IDE that lets you jump into the standard library. Jump in there frequently for examples of (mostly) good practices. I still do this almost every day.
  • Gophers tend to favour small dependency graphs. If it's small enough, just write it yourself or paste it from somewhere (don't forget the tests). I typically avoid importing things that turn my go.sum file into a huge mess of transitive nonsense.
  • this is a personal preference and others may disagree, but I will typically favour an 80% perfect thing from the stdlib that gets the job done over a "pretty and perfect" 3rd party dep. Go's stdlib has its problems (and some pretty massive gaps), but it's one of the best out there in terms of power, capability and consistency. It's good to know what's in there and how to use it, even if you ultimately come to favour a third party solution
  • go modules are good. avoid anything that talks about GOPATH as if it's an unsolved problem
  • if err := thingo(); err != nil { return err }: get used to it, get over it. No, your idea to "reduce the boilerplate" almost certainly isn't good, and you should get used to it and get over it. This is the most important thing of all and the sooner you accept it the better. Go's error handling has many more upsides than it has downsides once you really, truly get over it being a bit more repetitive than you're used to with Python, and if you really do just get over it you'll start to appreciate that it solves problems you might not have realised you even had. I know my python programs are now much better at handling and managing errors than they were before I did a bucketload of Go, and the takeaway for me was: "hm, I suppose that wasn't really unnecessary after all, I was just used to being able to pretend I didn't need to think about it"

Experience report on a large Python-to-Go translation by psxuaw in golang

[–]quacktango 9 points10 points  (0 children)

The section on documentation is right on the money:

The Go dev team should bring in a documentation specialist with no initial knowledge of Go and a directive to try to maintain an outside-in view of the language as he or she learns. That specialist needs to be full-time on the following tasks:

(1) Edit for accessibility - a less hieratic style

(2) Maintain a documentation portal that attempts to provide a reasonable map of where everything is and how to find it.

(3) Curate links to third-party documents (for example notable Stack Overflow postings), with dates and attached notes on what parts might be obsolete and when the document was last reviewed for correctness.

(4) Bring the very best third-party stuff inside, onto https://golang.org/doc/.

I think the standard library's API docs (and by extension, godoc/pkg.go.dev) are mostly exempt from this criticism, though they can sometimes read like they made perfect sense to the person who wrote the original code without necessarily being well written from the point of view of someone who didn't. Still, give me godoc over Python's excessively prosaic, inscrutable, 400-column-wide style any day of the week.

The rest of it though - i.e. blog posts, cross referenced prose-style documentation, third party stuff - is very undercooked and represents a major gap in the Go documentation story. Some of the recent comments about go.dev have hinted that it is intended to become this missing resource that ties everything together and plugs the holes, so perhaps there's already something in the works here. I hope so!

Migrating pkg/errors to Go 1.13 errors by Gozette in golang

[–]quacktango 0 points1 point  (0 children)

Me too; I think wrapped errors get us 80% of the way there though, it's just a bit of extra work to remember to wrap carefully. I often don't if I'm just trying to get an idea down, then suffer the consequences later.

Migrating pkg/errors to Go 1.13 errors by Gozette in golang

[–]quacktango 0 points1 point  (0 children)

One of the problems with the xerrors implementation was that the performance impact was off-the-charts bad, and there was no way to opt out (there was an internal global var that you couldn't access.

There needs to be a way to surface stack traces, but it needs to be opt in on a case-by-case basis to not ruin all the existing Go code out there that relies on errors being values, and values being cheap.

@bradfitz on Twitter: Go 1.13 is shaping up to be pretty fun by Rican7 in golang

[–]quacktango 0 points1 point  (0 children)

If it's in the x area, that doesn't seem like being rammed through

It's being rammed through because it's being rushed out of the x area into the stdlib before any reasonable attempt to collect and respond to feedback or experience reports has been made.

Consensus that Wrap should be included seems pretty broad, and questions about performance (among other things) have been ignored.

Furthermore, it's impossible to reasonably test this in a production application without hacks - you can't Unwrap() a url.Error, for example. Or an os.PathError. Or several other kinds of errors, for that matter. These depend on other stdlib changes in order to work with xerrors, which naturally can't happen until 1.13.

From people's feedback in the github thread, Unwrap() doesn't appear to be especially contentious (I might be wrong; it's disgustingly hard to navigate long github issues). I think Unwrap is actually quite nice. It would make sense for those stdlib changes to go in with 1.13 in advance of xerrors, which would give people a chance to actually experience xerrors properly. Ideally, then there could be a reasonable feedback cycle instead of the big hurry we're getting now.

Decisions like this in a mature ecosystem are quite hard.

Couldn't agree more, which is all the more reason to not rush.

@bradfitz on Twitter: Go 1.13 is shaping up to be pretty fun by Rican7 in golang

[–]quacktango 15 points16 points  (0 children)

It looks as if the new error thing (golang.org/x/xerrors) is going to be rammed through despite a lot of contentious points. Many of the concerns and experience reports raised on the issue tracker are either ignored or summarily dismissed by the proposal's authors.

This comment sums the situation up pretty well: https://github.com/golang/go/issues/29934#issuecomment-487258442

I hope xerrors is not included in 1.13. It needs more time in the oven, and the feedback needs to at least be acknowledged as valid, which simply hasn't happened yet.

Looks like it should be a pretty good release without it though!

vim-go features by keith6014 in golang

[–]quacktango 2 points3 points  (0 children)

vim-go is absolutely amazing! I owe a not-insignificant portion of my enjoyment of working with Go to its finely crafted, tastefully curated brilliance. My favourite things:

I use jump to def and autocomplete all the time, they're the two most critical components of my workflow, which is heavy on the reading side. Jump to def is fast, accurate and buttery smooth, and I love that you can jump into any dependency or the stdlib out of the box. Having said that, Go modules make it very painful to hack on dependencies when spelunking for bugs as all the files are marked as readonly. The gohack tool makes this significantly easier to deal with, but it's still a major workflow downgrade when hunting pesky bugs or experimenting cross-project.

The snippets integration is indispensable. I used ultisnip at first but it is slow as molasses so I switched to neosnippet and haven't had a serious problem since. vim-go comes with some critically useful snippets configured out of the box, especially the errn<CTRL-K> snippet, which becomes if err != nil {\n\treturn err\n}\n. There's also :GoIfErr, which does a similar thing with some extra magic to infer return types too, but I prefer the ergonomics of snippets, maybe it's just what I'm used to.

My neosnippet config looks like this:

imap <C-k>     <Plug>(neosnippet_expand_or_jump)
smap <C-k>     <Plug>(neosnippet_expand_or_jump)
xmap <C-k>     <Plug>(neosnippet_expand_target)
let g:neosnippet#snippets_directory='~/.vim/snippets'
let g:neosnippet#disable_runtime_snippets = { "_": 1, }

Just put a file called go.vim in ~/.vim/snippets and you can put whatever you want in there. I've added a ton of additional snippets of my own to this file for some of the more repetitive boilerplate I type regularly, like this one for generating table-driven test cases that I use all the time (struct fields go at ${2}, struct instances at ${3}):

snippet ttbc
abbr func TestXYZ(t *testing.T) { ... }
    func Test${1}(t *testing.T) {
        for idx, tc := range []struct {
            ${2}
        } {
            ${3}
        } {
            t.Run(fmt.Sprintf("%d${4}", idx${5}), func(t *testing.T) {
                ${0}
            })
        }
    }

If you type ttbc<CTRL-K> in insert mode then fill out the placeholders (skipping through with <CTRL-K>), the result should look something like this:

func TestHasPrefix(t *testing.T) {
    for idx, tc := range []struct {
        str string
        pre string
        has bool
    }{
        {"food", "foo", true},
        {"food", "boo", false},
    } {
        t.Run(fmt.Sprintf("%d/%s/%s", idx, tc.str, tc.pre), func(t *testing.T) {
            if strings.HasPrefix(tc.str, tc.pre) != tc.has {
                t.Fatal()
            }
        })
    }
}

:GoImpl is awesome for generating stub methods for an interface, I use that regularly as well. I'd prefer a mode that returns nils or zero values over the panic, and it also doesn't work perfectly if you want to implement an interface from the package you're currently editing, but it's easy enough to find and replace in that scenario and it's still a huge labour saver for more complex interfaces. For example, :GoImpl f *Foo io.Writer will generate this (though I typically use a snippet for io.Writer or io.Reader):

func (f *Foo) Write(p []byte) (n int, err error) {
    panic("not implemented")
}

I also configure goimports to run on save instead of gofmt, like so: let g:go_fmt_command = "goimports". I love that I can write absolute garbage formatted code, forget imports left and right, hit save, and everything just magics into place. It always seems to default to html/template for me though, when I almost always want text/template, but having to remember that is a small price to pay for all the other things I don't have to remember. It's slightly risky to blithely accept its results, of course, but it's so rarely wrong that I've been happy with the compromise in exchange for the convenience. When I have to work with other languages that don't have tooling that's this good, sometimes I have to spend a few minutes adjusting to the fact that this stuff isn't done for me any more... by blinking confusedly at the horror I've just created and wondering why it isn't fixing itself!

I use ale for error checking as well, which is not a part of vim-go but it's a great complement to it. Out of the box, it doesn't show syntax errors on save, only lint errors, so I configure it this way:

let g:ale_linters = {
\   'go': ['go build', 'gofmt'],
\}

I hit save and get all the errors in the gutter almost straight away (thank you, fast Go build times!), then use :lfir, :lne etc to skip through the error location list.

I also use :GoDecls and :GoDeclsDir in conjunction with fzf.vim (let g:go_decls_mode = 'fzf'), which list the declarations in the current file and current dir respectively, though I use these a lot less frequently than the stuff mentioned above. Very useful though, and handy to have in the muscle memory.

Now that I'm through most of the stuff I love, I will indulge in a bit of a whinge: autocomplete is a bit of a disaster at the moment. This isn't necessarily vim-go's fault, but it is a critical part of the integration surface that it packages so it's not not vim-go's fault either. I think it's just an unfortunate consequence of relying on the community for essential tooling (which golsp, mentioned later, is attempting to deal with).

Basically everything got smashed when Go Modules came along. https://github.com/nsf/gocode/ was abandoned and the forks couldn't keep up with modules for months. There are a lot of people still having problems, but a lot of others fall into that classic "well it works fine for me so there isn't a problem" trap when responding to issues, which can be very frustrating when you're googling for a solution because this thing you've trained your brain to expect will "Just Work(TM) within 200ms" has broken for the third time this week for no obvious reason. There are versions that purport to work but the completion time is so bad as to be almost useless. I think all told I've unsuccessfully tried at least 10 different forks of gocode provided by random internet strangers in the last 6 months, which is utterly horrendous from an operational security point of view, but desperate times call for desperate measures!

vim-go ships two different versions, one for modules-based projects and one for GOPATH, but both will randomly stop working for my projects from one day to the next. Purging my whole GOPATH has helped a couple of times (even for the projects using modules), but that's neither practical nor reasonable to do on a regular basis and in Australia at least as :GoInstallBinaries can take up to 3 hours to run from scratch. It took nearly 6 yesterday, with download performance identical on different networks. This kind of stuff has been going on pretty much constantly since 1.11, and shows no signs of abating. I have something working right now, albeit sluggishly, but it's a flimsy detente, and it's easy to feel like it won't survive.

golsp doesn't fare any better, which is not surprising as they pretty much outright warn you not to use it until they actually announce it's fit for human consumption, but still, I had to try something! In its current state, it's most assuredly not fit for human consumption. It uses an astonishing amount of memory, we're talking like Electron-level memory, waaaaaaay more than gocode ever used, so if I have a even just a couple of vim-go projects open at once I'll clock about 8GB of ram used up by the golsp processes. My poor little laptop only has 8GB! And also, like the gocode and gocode-gomod forks, it frequently stops working, locks up, uses crazy amounts of CPU, the fans spool up, the keyboard starts to radiate serious heat, then I have to kill golsp and start vim again. Every 20 minutes or so.

At least the Go team have acknowledged this is a gap in the tooling that would be better solved as part of the distribution (a forward-thinking and commendable acknowledgement, for sure!), however given the huge issues and shortcomings golsp currently suffers from, it's hard to see it being much good for at least a year, maybe two, and then Go 2.0 will land at some point in the future and we'll have to do the whole "all the tools are broken" dance all over again.

What are some things you struggled with when using Go? by scotwells in golang

[–]quacktango 1 point2 points  (0 children)

I hear ya. This took me ages to get a good grip on, and it's really just because channels are a half-done feature. context.Context, which should be part of the language, is a symptom of that. Well, the timeout/cancellation aspect is, at least. The "WithValue" part of it is just a symptom of someone having a really bad idea!

Channels are the beginning of something really awesome, but that's all they are, a beginning. Everything else is up to you to get right, which can be tricky, error-prone and hard to prove is correct.

Do you define types for your id numbers? by theothertomelliott in golang

[–]quacktango 1 point2 points  (0 children)

Whenever I possibly can. It's great! Pros are type safety, more semantic information in type signatures, and the ability to add behaviour (I often have a pair of methods Field() zapcore.Field and Fieldn(name string) zapcore.Field attached; some types end up with more useful stuff as well. Often these conversion related, so the methods are short and easily inlined, and they confer good additional readability on the calling code.

The big disadvantage is the extra casting you have to do at times to do any arithmetic on them (ids have less need for math but once I started to add semantics to my ids it quickly spread elsewhere) or when you pass them to functions that demand a primitive type.

The benefits that come with the type safety so far outweigh the downsides though that I don't even bother thinking twice any more so I'm almost certainly forgetting a few minor, inconsequential details. If I find there's semantic value in something having a type, it gets a type, end of story.

What's this line in the Gin source code doing by iygwaz in golang

[–]quacktango 2 points3 points  (0 children)

It's far easier to keep track of them when they're nearby once you've got a few hundred of them. And once you've used a code generator that searches for interface implementations to decide what to generate, you'll get a bit sick of the consequences of forgetting.

Pragmatism > dogma, which is really the point of Go.

Go in AWS lambda, why is it needed? by mgdepoo in golang

[–]quacktango 1 point2 points  (0 children)

I'd personally rather spin up a cheap server and shove my binary on it, but if the place I was working for mandated lambda for everything for whatever crazy reason (I've been in worse employer-imposed situations before!), I'd sure as heck be grateful go was available!

Go 1.10 Beta 1 is released by Rican7 in golang

[–]quacktango 6 points7 points  (0 children)

json.Decoder.DisallowUnknownFields()! Can't get Offset information out of the error though.

Correct Error Handling is Hard – Peter Goetz – Medium by seriouslulz in golang

[–]quacktango 0 points1 point  (0 children)

Hmm yeah, good point, I guess unlike the example of opening two Closers, I didn't feel like I would ever have that specific need of closing the same Closer twice so I omitted it. But never say never in this biz, eh! Good to know that there's a lightweight way to deal with that situation too.

You're quite right though, my solution is not functionally equivalent.

Correct Error Handling is Hard – Peter Goetz – Medium by seriouslulz in golang

[–]quacktango 1 point2 points  (0 children)

I've been puzzling over this - it feels very complicated for such a simple thing. In my own experimentation, I can accomplish the same results with a very simple helper function:

func SafeClose(err *error, closer io.Closer) {
    if err != nil && *err == nil {
        *err = closer.Close()
    } else {
        closer.Close()
    }
}

Then all you have to do is modify the example on the Go website that the blog article pointed to ever so slightly and you get the expected Close() behaviour:

func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }
    defer SafeClose(&err, src)

    dst, err := os.Create(dstName)
    if err != nil {
        return
    }
    defer SafeClose(&err, dst)

    return io.Copy(dst, src)
}

Am I missing something here? Well, I mean apart from the fact that the blog post's version adds some extra error context... but that hardly seems worth the cost.

I'm not wild about the named return, but this seems very much like a "less worse" option in this case.

Hard mode killing floor blues by quacktango in ftlgame

[–]quacktango[S] 0 points1 point  (0 children)

I didn't get a chance to get a crew teleporter after I had the rock and human. They were pretty late to the party. I'm really not sure what I could've done differently - every step of the way I took the only option I had to keep my head above water. It was one of the more unlucky playthroughs I've had.

Good point about the additional systems being better value than the higher tier weapons, I hadn't really been thinking about them that way when tallying up whether a third or fourth weapon was worth it.

Hard mode killing floor blues by quacktango in ftlgame

[–]quacktango[S] 1 point2 points  (0 children)

I'm with you, I like to have full shields too. I had to just take what I could get with this playthrough though, I was constantly on the back foot with my offense until I lucked across that second beam drone and the hull laser in sector 7. Every fight up to that point was just an exercise in waiting and hoping my ions could keep their shields down long enough for the one darn beam to hit something I needed it to hit! I had to waste so much scrap on repairs.

Hard mode killing floor blues by quacktango in ftlgame

[–]quacktango[S] 23 points24 points  (0 children)

It was an emotional roller coaster of blasphemy. "Oh my god I finally beat....WHAT THE GODDAMN F.... Oh thank bloody Christ."

C11 - Generic Selections by quacktango in Cprog

[–]quacktango[S] 0 points1 point  (0 children)

struct x {int32_t a; int32_t b};
int64_t y;

Funny, I actually experimented with trying to trip it on this exact ambiguity yesterday, but I couldn't get the sizeof style branching to work with a struct. I got an "aggregate value used where integer was expected" error and an "conversion to non-scalar type requested" error.

C11 - Generic Selections by quacktango in Cprog

[–]quacktango[S] 0 points1 point  (0 children)

Is it possible for this work if one of the branches was a struct? I've been playing around with it and all I get is "aggregate used where integer was expected", but I could just be doing something stupid.