I'm new to Go within the past year or so. Every time I start thinking about some fancy sugar I got from another language, I've basically realized that I didn't need it. The ease of "build" and "run" is amazing to me, and when I find I need to do something fancy in my code, I generally find that I don't actually need that.
question for experienced go devs: how do you deal with the lack of optional type or narrowing?
from what i understand, pointers become used for that purpose instead (in addition to mutability). which then means that when you get a pointer you don’t know for what reason it is used, so you have to check it even if it was checked higher up in the callstack already.
i wouldn’t actually mind the fact that you can forget to check for nil, i can accept a panic here and there, pretty simple to fix..
the issue i am finding is that it "pollutes" the code, since you end up doing the check more than once for the same value.
if atleast you could only check once and then the compiler would know (ie optionals, or narrowing like TS). ideally you would dereference after the check, but you can’t because pointers are used for mutability AND optionals
in other words, are there any tricks so that you only have to check once?
Check it once and type cast it before passing it down the call stack. If you need a polymorphic type all the way down then model it using structs and interfaces so that the required operations are always available as methods.
It’s the same as other languages which lack optionals/narrowing: C/C++, Java, etc.
Say i have `*Thing`. Are you saying i should cast it to a `Thing`? That is what I was doing initially (im fine with copies), but then you can't call functions that mutate.
Soon after generics were introduced, I put a generic Maybe type in our team's codebase. Its been very helpful.
Other than that though, I would say pointers are a last resort for indicating optional (I certainly do use them for this, but uncommonly). Idiomatic Go I think would suggest you use the empty value (I would use an empty value plus an // Optional comment).
Using pointers in general is fairly uncommon in the Go code I write - I don't often need the mutability as you say, because I try to stick to pure functions.
My thinking also, regardless of the language: be suspicious of a function that accepts an optional type. One should ask: is there a non short-circuit or non-error path in the empty case? If not, don't pass an optional, unwrap up the chain. Even if there is a valid path for an empty value, perhaps ask if the function should be split into two cleaner functions.
>question for experienced go devs: how do you deal with the lack of optional type
Perhaps they're used to use, as most mainstream languages didn't use to have it and many still don't (or just have it as a seldom used afterthought, like Java)?
> how do you deal with the lack of optional type or narrowing?
The idiomatic way is to return an error or an `ok` boolean at the function boundary. Callers always check return value anyway, this is hard to forget because it’s almost always needed anyway.
The type itself can be value or pointer – its an orthogonal choice. If you have a value type that you return, then you should return the zero value (for instance `time.Time{}`. Both nil and the zero type are comparable by equality check.
This is not ideal, and gets messy when the zero type is a valid value for instance. Generics offer some hope but they are not as capable as in eg Rust.
Hmm, are we talking json parsing? Yes, the field would take the zero value. If you need to differentiate between the zero-value (eg empty string, 0 for numbers) you’d typically use a pointer only for that purpose. That kinda sucks but there are some good arguments for zero-defaults too.
The unmarshal functions take a reference but they don’t need to be declared as (heap-based) pointers. You can pass a `&val` directly. It needs a reference passed in to determine the type.
In many cases, you don’t really need to check pointers unless you specifically want to handle the nil case. Go’s panic / recover feature lets you “catch” nil pointer access elsewhere. For instance, if you have an HTTP handler, it’s way easy to install a middleware that recovers from panics than it is to check for nil everywhere.
The point of having it in the type system is not just to force you to check it. It is also to clearly document where you need to check it, and just as important, where you don't.
Without it, you don't have that "this is guaranteed to exist at this point" indication that can actually reduce the overall number of checks in practice.
I agree, it seems common for people to learn "Hello, world" in 200 languages rather than diving deeper into one. I also understand why this happens, it's exciting and new, but I think the latter is more beneficial.
How about good experience in about 5? And took an undergrad Programming Languages class?
Going from Python (and not a comp sci background it appears superficially) to GoLang isn't some broad journey of deep insight into programming languages, not just from the Lisp zealotry, but to know the practical advantages of things at enterprise scale.
Has the line between low-level and high-level languages moved? I remember being taught that a low-level language is like Assembly language while a higher level is C. And I still stick to that, but is that not the consensus anymore?
Also, why do we talk about static binaries like it's a new feature? Is it really that exotic nowadays?
The line has definitely moved but not so far that Go would be considered low level. I think most people would consider languages without any sort of garbage collection to be "low level" now so C, C++, Rust, etc.
Go used to (still?) market itself as a "systems programming language" and I think that along with the presence of pointer syntax gives some the initial impression that Go is a scary low level language.
In fairness to the author of this article, he did call it low level as a comparison to Python and mentioned that it not as low level as C which is all true.
28 comments
[ 5.2 ms ] story [ 76.1 ms ] threadHope this doesn't mean we are in a cult ;)
from what i understand, pointers become used for that purpose instead (in addition to mutability). which then means that when you get a pointer you don’t know for what reason it is used, so you have to check it even if it was checked higher up in the callstack already.
i wouldn’t actually mind the fact that you can forget to check for nil, i can accept a panic here and there, pretty simple to fix..
the issue i am finding is that it "pollutes" the code, since you end up doing the check more than once for the same value.
if atleast you could only check once and then the compiler would know (ie optionals, or narrowing like TS). ideally you would dereference after the check, but you can’t because pointers are used for mutability AND optionals
in other words, are there any tricks so that you only have to check once?
Say i have `*Thing`. Are you saying i should cast it to a `Thing`? That is what I was doing initially (im fine with copies), but then you can't call functions that mutate.
Other than that though, I would say pointers are a last resort for indicating optional (I certainly do use them for this, but uncommonly). Idiomatic Go I think would suggest you use the empty value (I would use an empty value plus an // Optional comment).
Using pointers in general is fairly uncommon in the Go code I write - I don't often need the mutability as you say, because I try to stick to pure functions.
My thinking also, regardless of the language: be suspicious of a function that accepts an optional type. One should ask: is there a non short-circuit or non-error path in the empty case? If not, don't pass an optional, unwrap up the chain. Even if there is a valid path for an empty value, perhaps ask if the function should be split into two cleaner functions.
Perhaps they're used to use, as most mainstream languages didn't use to have it and many still don't (or just have it as a seldom used afterthought, like Java)?
The idiomatic way is to return an error or an `ok` boolean at the function boundary. Callers always check return value anyway, this is hard to forget because it’s almost always needed anyway.
The type itself can be value or pointer – its an orthogonal choice. If you have a value type that you return, then you should return the zero value (for instance `time.Time{}`. Both nil and the zero type are comparable by equality check.
This is not ideal, and gets messy when the zero type is a valid value for instance. Generics offer some hope but they are not as capable as in eg Rust.
The unmarshal functions take a reference but they don’t need to be declared as (heap-based) pointers. You can pass a `&val` directly. It needs a reference passed in to determine the type.
Without it, you don't have that "this is guaranteed to exist at this point" indication that can actually reduce the overall number of checks in practice.
What I like about Go as a former python programmer
So ... I'd care more about the uh insights in this article if he has programmed in more than two languages.
Going from Python (and not a comp sci background it appears superficially) to GoLang isn't some broad journey of deep insight into programming languages, not just from the Lisp zealotry, but to know the practical advantages of things at enterprise scale.
Brian Kernighan just called to say that any well-designed libc should support static linking.
And, for some reason, glibc does not.
Has the line between low-level and high-level languages moved? I remember being taught that a low-level language is like Assembly language while a higher level is C. And I still stick to that, but is that not the consensus anymore?
Also, why do we talk about static binaries like it's a new feature? Is it really that exotic nowadays?
Go used to (still?) market itself as a "systems programming language" and I think that along with the presence of pointer syntax gives some the initial impression that Go is a scary low level language.
In fairness to the author of this article, he did call it low level as a comparison to Python and mentioned that it not as low level as C which is all true.