90 comments

[ 3.1 ms ] story [ 204 ms ] thread
Finally, I can keep my Go projects wherever I want.
Ech, I like to submit to GOPATH. One less thing to think about.
I missed any reference to GOPATH. Does this release fixes Go's problem of forcing developers to specify source code directories through GOPATH?
That is correct. Now you can clone and build Go repos anywhere.

Note that support is still experimental, so if you want stability, it is advised to remain inside GOPATH for another release. This is a good post - https://systemdump.io/posts/2018-07-22-go-modules

However, do please feel free to try it out and file bugs.

The GOPATH nonsense was the reason why I've decided to take a hard pass on Golang, and instead pick up Rust. I'll stay away fron Golang until that braindead idea is officially dead and a thing of the past.
A change to gofmt like that is going to require a large formatting commit to conform. Presumably/ideally there are CIs that fail on invalid format, so it's not like you can wait until you touch the file again. Not a big deal, but still worth noting.
There was a change to gofmt in 1.10, too: https://golang.org/doc/go1.10#gofmt

"""Note that these kinds of minor updates to gofmt are expected from time to time. In general, we recommend against building systems that check that source code matches the output of a specific version of gofmt. For example, a continuous integration test that fails if any code already checked into a repository is not “properly formatted” is inherently fragile and not recommended."""

> For example, a continuous integration test that fails if any code already checked into a repository is not “properly formatted” is inherently fragile and not recommended.

As a frequent repo/commit spelunker, I'm gonna have to disagree here. It is better to have this changed all in one commit instead of tacking on that formatting change along with an unrelated change elsewhere in the file and doing that all over the place at separate times. What's inherently fragile (formatting wise) is making a change to a file that does not meet the current formatting requirements and then having it format on save/commit, messing up blames, code review diffs, etc.

Yep -- formatting for style is great iff it’s done rigorously and consistently on every commit (which is what a presubmit check is good for). Ad-hoc reformatting mingled in with real changes plays havoc with change tracking.

Ideally the format would never change at all. An occasional change-the-world update is a reasonable compromise, but it should be done as a single commit with no logic changes.

Ideally, CI won't fail on invalid format, but just politely apply the correct format. There's nothing more frustrating than being told to do a job a machine can trivially to better (like executing "gofmt").
Right, so, checks and balances.

Server-side you have the check.

Client-side you have a hook that runs gofmt when you commit, and in principle you’ll never hit the server-side check.

Just in case something goes wrong -- and something somewhere always goes wrong -- you don’t want the server to silently rewrite stuff on your behalf.

Go is missing 2 things that I care about:

1) proper dependencies management system (the new module system might address this) and

2) gener... nah, just kidding -- actually null protection. I don't want to see another segfault or null pointer failure ever again.

If it had these two features, I'd probably stop toying around with Rust and Crystal and just use Go. Unfortunately, 2) is probably impossible without seriously breaking old code, and they're not gonna break old code.

Why not try Kotlin. I don't understand why people use Rust for non System programming
When have you seen segfaults in Go? I think 100% of the time, it is either a bug in the Go runtime, or you're using cgo (which is not really Go anymore).

A language as simple as Go without nilable types would be great, though.

nil pointers exist in Go. It is common to run into them, especially when using "&".
C programs tend to call nil-pointer panics "segfaults" even though go runtime doesn't print the word "segfault" anywhere. I'm guilty of the same thing.
Well, I think they're worth separating.

I've seen a raw SIGSEGV in Go due to a runtime bug years ago, and the only safe outcome is process termination.

On the other hand, dereferencing a nil value in Go in relatively safe (as in Java), all it does is crash the goroutine. For common cases like HTTP and RPC handlers, no big deal, safely recoverable.

Edit: on second thought, forget that. I agree that it's not a good thing (not safe) for recovery from goroutine crash to be possible.

> dereferencing a nil value in Go in relatively safe (as in Java), all it does is crash the goroutine.

Yeah, these are exactly the things I don't want to see: production log messages saying that something or other referred to a nil pointer and thus crashed some process. I don't think they belong to modern programming.

Sure, segfaults are even worse since you don't even get the log message, but I still kinda lump both of these problems together as "errors from the 1900s that shouldn't happen anymore".

Totally agreed. A modern type system should make these errors fundamentally impossible. If you see them in production, it means there is definitely a logic or error handling issue somewhere in your code and I find it weird that there's no support for a known technique for eliminating them.
You can't actually accomplish that without making the language really complicated.
Are Crystal, Kotlin and Swift really complicated? Is Rust's complications due to null protections or something else?
Are you joking? They're all way more complicated than Go.
Okay, let's pretend that's true.

Are the complications due to null protection being there, or are they due some other things?

Some of the complications are necessary. You either need generics or a whole bunch of primitives to have non-nullable references. Go would also have to replace slices with something different. Return types and the shape of the interfaces would have to be changed. Algebraic datatypes or some complicated way of defining return types would need to be added for it to be usable. Defer statements that can modify the function's return value would need to be handleable by the type system, or dropped.
NULL exists in every programming language. There must be something to designate a missing value, a non assigned pointer or reference.
Most languages, sure, but not all; Haskell doesn’t have NULL. You can express the absense of a value with a Maybe:

    Just “value” -> Maybe String
    Nothing      -> Maybe a
If your function returns a “Maybe String”, it can either be just a value, or Nothing. However, if a function returns a “String”, you can’t return Nothing, only an actual String.

There’s no null in Haskell.

yeah... no. most notably rust, which might be surprising as it can be used (and has been) to write a kernel in, only has null pointers in unsafe code.

rust is hardly the first one, too.

> NULL exists in every programming language.

No.

> There must be something to designate a missing value, a non assigned pointer or reference

Option types are something like 45 years old, they're not exactly futuristic technology.

There has to be a way to represent a missing value, but the key point in languages that don't have NULL is that you can only represent missing values using a type that supports missing values - e.g. option types such as Haskell's Maybe.

This achieves two things:

1. It allows you to define functions that cannot return NULL, and the compiler guarantees this. This is a huge win - you no longer have NULL as a possible return value for many functions.

2. Values belonging to the option type have to be unwrapped to access their contents, so it's not possible to unknowingly dereference a missing value. This is an even bigger win than #1, because it can literally eliminate null pointer errors.

A caveat to #2 is that depending on the language, it may be possible to override safety, for example Haskell's fromJust can throw an exception if used on a missing value. But this tends to require conscious effort, and can easily be checked for automatically, since it involves the use of unsafe function calls.

> A caveat to #2 is that depending on the language, it may be possible to override safety, for example Haskell's fromJust can throw an exception if used on a missing value. But this tends to require conscious effort, and can easily be checked for automatically, since it involves the use of unsafe function calls.

A much scarier example there is C++, where std::optional can be deref'd (literally `*opt`, the easiest and shortest thing you can do with it)… and just UBs on an empty optional. Because C++ hates both type-safety and memory-safety.

It is extremely easy to get a SIGSEGV by creating a double pointer and then attempting to assign something to *p.
Couldn’t agree more.

Nil exception handling is horrible in go. The thing wasn’t designed to handle pointers and blows up left and right once someone tries to use them (or untyped objects).

Dependency management is a total mess.

Try out dep for dependency management. It's still young, but is getting better all the time.
The new official way of package management is vGo. Dep was an experiment which was suucceded by vGo, which in 1.11 is officialy (beta) included in the Go toolkit and will be the main way in Go 1.12.

There's a great blog post series about it (though it's dense, plan to spend a few hours to read it all: https://research.swtch.com/vgo)

Why isn't Go implementing Optionals like Swift with guard let, if let, etc.?
Because it would need generics
No it wouldn't. There are already built-in generic types like map[K]V and slices. If the Go Team wanted, they could easily add built-ins like option[T] and result[T]. But in Go 1.0 they would stand out like a sore thumb, because the stdlib wouldn't use them.

Maybe for Go 2.0.

I would like to see a set library as part of standard library if they are going to do something more on this front.
It seems to me that's just a map[set_key]bool
1. because you can't build opaque abstractions in Go, you have to wonder if there's a difference between having a true and a false value mapped to a key

2. because sets are significantly less useful without set-operations (intersections, unions, differences) and since go does not have user-defined generics you can't build a generic version of those (

3. because it wastes 1 byte per item (according to the comments in bmap, buckets are implemented column-major

`map[K] struct{}` would fix 3 and the second half of 1 (struct{} is a zero-sized singleton) but is somewhat less ergonomic so the impact of what's left of 1 is worse, and it doesn't solve 2 at all

I would be more radical and add them as a part of the language, the way map is now. AFAIK, Wirthian languages like Pascal and Oberon all have sets as built-ins, with +, -, and * operators defined on them, and Go is heavily inspired by them.
It could be a special-cased builtin like maps and slices and channels.

AFAIK the reason — much like C# — is their adherence to the concept of ubiquitous defaults / zero values: as far as Go is concerned all types have a zero-value (a non-overridable default value when not explicitly initalised), and as far as its community is concerned zero values are a good thing and must be embraced.

default zero is good, but default zero pointer... ugh
> default zero is good

Default zero without it being opt-in is terrible.

> but default zero pointer… ugh

That's what ubiquitous default zero gets you.

Re. "null protection": It is kind of hard to fit non-nullable pointers into Go. The fact that non-nullable pointers do not have a natural initial value and always require an initial assignment is especially problematic in Go's struct semantics, because first of all, struct creation is supposed to be light-weight. If a struct contains pointers, the cheapest way to initialize it would be to assign them nil.

But the more interestingly problem is how Go's struct "constructor" works. It is very low-level, you just assign some fields in a struct like &SomeStruct{X: 1, Y: "y"}; you can also assign all fields, in which case you can omit the keys, like &SomeStruct{1, "y"}. let's call those the key-value variant and positional variant of "free-form constructor".

You can provide a "function constructor", which is simply a function that returns SomeStruct or * SomeStruct, but it is not required, nor can you forbid the free-form constructor.

This is in stark contrast with, say, Java, where the function constructor is both required and the only allowed form. That makes it easy to make sure that non-nullable pointer fields are always initialized, because the compiler only needs to check the constructors.

In Go it is much harder. You can make the free-form constructor check that non-nullable pointer fields get initial values, but then Go has this promise that the author of a struct can add new fields without breaking users of the free-form constructor, as long as they are using the key-value variant instead of the positional variant. So if a struct was originally:

type SomeStruct { X int Y string }

A user can make an instance with &SomeStruct{X: 1, Y: "y"}. If the author of the struct decides to add a new field, the code should still work. That should be the case even if the new field is a non-nullable pointer field. This now defeats the purpose of non-nullable pointer fields.

> but then Go has this promise that the author of a struct can add new fields without breaking users of the free-form constructor, as long as they are using the key-value variant instead of the positional varian

This is the bit that would need to change. And actually, doing so would provide the same benefits that non-nullable types do in other contexts.

Say a Go library user is using a struct in 5 places, and the library author adds a new field. The user updates the free-form constructor to properly initialise the new field in 4 of those 5 places, but overlooks the 5th. As it stands, that will fail silently if the program is relying on using a non-null value somewhere else in the code. If upgrading the library caused a compile time error then that bug would have been caught.

Rust also has "free-form constructors" like Go, and this is the approach it takes: you must provide a value for all fields.

Go has a pervasive concept of “default being zero” where people are encouraged to write code so that zero-initialization is valid and reasonable. This means that, when adding a new public field to a structure, you always make sure that the default value (nil for pointers) is valid and backward compatible with the previous behavior (eg no change in behavior).

Rust has a half backed support for default initialization, where you can either mark all fields as default initialized via a macro or none (or you can mark them and add a constructor that only changes some, but then the API is confusing for clients unless default initialization is a valid construction). This means that it’s much more common to write constructors, and being forced to set many fields to 0, empty string, None, ecc. is tedious and factually useless; the provide support (Default macro) is not powerful enough to avoid this, unfortunately.

I strongly prefer Go’s approach to default initialization in general, and I think it is orthogonal to non-nullable types (you could have both and save the day). For instance, I don’t see what we lose in Rust by saying that integers auto-initialize to zero unless you name them in the constructor.

> Rust has a half backed support for default initialization, where you can either mark all fields as default initialized via a macro or none (or you can mark them and add a constructor that only changes some, but then the API is confusing for clients unless default initialization is a valid construction).

So it has half-baked support because… you can opt into exactly how Go behaves but it's not foisted upon you?

> This means that it’s much more common to write constructors, and being forced to set many fields to 0, empty string, None, ecc. is tedious and factually useless; the provide support (Default macro) is not powerful enough to avoid this, unfortunately.

Be the change you want to see in the world and open an RFC for partial defaulting?

It's already possible to "fill in" literal struct with `..Default::default()`, if you can defend there being a use-case for it, this would be extendable to filling in default-able fields even without an explicit (and public) Default implementation for the struct. There was an issue on the tracker in the past (https://github.com/rust-lang/rust/issues/42692) but the change is big enough that it would require an RFC, actual design and consensus, but that's the extent of it.

Alternatively, build a macro doing that for you. There might already be one but a quick cargo search didn't turn anything up.

> I strongly prefer Go’s approach to default initialization in general, and I think it is orthogonal to non-nullable types (you could have both and save the day).

How can you have non-nullable fields with a zero value exactly?

> For instance, I don’t see what we lose in Rust by saying that integers auto-initialize to zero unless you name them in the constructor.

The regularity of the language to start with.

> first of all, struct creation is supposed to be light-weight.

Non-nullable pointers don't make struct creation any more expensive.

> You can provide a "function constructor", which is simply a function that returns SomeStruct or * SomeStruct, but it is not required, nor can you forbid the free-form constructor.

You can have an exported function returning a non-exported type. This restricts the "freeform" constructor to the package's internal's. It severely limits the caller's ability to use it (unless you also provide an interface to work with), but it's there.

Totally agree on 2. "Learn a language a year" they said. "Learning new languages expands your thinking" they said. What they didn't say was that once you use ADTs to model data, Maybe/Option types to avoid null, exhaustive pattern matching, etc, you want those features in all the languages you use.

I don't ask for much. I just want a GC'd Rust (one of the few modern languages that has option types in the std lib) or an F# that isn't tied to the .NET standard lib. Please?

> F# that isn't tied to the .NET standard lib. Please?

F# is basically OCaml on .Net, so… OCaml?

I just made this choice (learn fsharp or oval). initially I was leaning ocaml for the same reason as gp but then I tried a simple scraper (make a get request and parse the HTML) and couldn't get it to compile because of cabal hell. well obviously not cabal hell but dependency hell where I had to keep changing installing and uninstalling versions of cohttp and the compiler. in the end I said screw it and installed all the .net/mono/f# stuff and it worked flawlessly the first time. I don't understand how language developers don't prioritize package management in 2018 - it's simply a deal breaker when other languages get it right. yes I realize ocaml and haksell have a different perspective on dependency resolution than other languages but then they should ensure that it works (and yes I also realize dependency resolution is np complete).
It can happen if you happen to install a recent compiler and that package manager has not done the work(probably changing the bounds) to update in opam repo.

> I don't understand how language developers don't prioritize package management in 2018

As you already know, Ocaml already has a decent package manager opam, this same situation will happen in any language where the packages get stale(e.g. in rust you installed something used nightly features that didn't make it mainstream).

The tooling around OCaml is nowhere near F#, let alone Rust. The standard library situation is less than ideal and they're still working on getting multithread support.
I actually stopped learning new languages with these features because I started to get so frustrated they weren't available. Or I implemented then am everyone got weirded out by their 'strangeness'
In regards to 2: I find it difficult sometimes to work with a language that doesn't have Elixir/Erlang's pattern matching, and sometimes I wish I hadn't learned about it...

(the pipe operator is another favorite I miss often)

There are proposals to add |> (pipe) to JS.
I think Swift is the closest thing to a GC’d Rust. However, since Windows isn’t a first class platform for Swift, I’d recommend checking out Scala.

I think they’re doing a compiler rewrite that steals the good ideas from Rust (like good error messages).

Terrible idea: could you write a transpiler for rust that basically auto-wrapped everything in a Gc<> type and auto-dereferenced?
Agreed with 2. I honestly don't understand why the compiler doesn't do analysis to catch such cases. As someone who doesn't work on these things, perhaps it's too intensive? I've made silly nil pointer mistakes before and wondered why the compiler didn't say 'hey dummy, look what you did here'.
>Last release where godoc has a command-line interface

What? Are they removing “godoc <pkg>”? I used that a lot as a man page. It’s especially useful when you don’t have internet access

Edit: it’ll still be available in `go doc`, just that `godoc` will only be a web server. https://tip.golang.org/doc/go1.11#godoc

> Some crypto funcs now randomly read an extra byte

Can someone explain how this is a feature?

It's to stop people misusing the crypto APIs, or making assumptions about them that the Go maintainers don't want to be stuck supporting.

https://go-review.googlesource.com/c/go/+/64451

> Code has ended up depending on things like RSA's key generation being deterministic given a fixed random Reader. This was never guaranteed and would prevent us from ever changing anything about it.

I respect agl a lot, but this really doesn’t make sense to me. Should I be able to rely on the RSA keygen being deterministic between versions, given a fixed random Reader? No. But should I be able to rely on it being deterministic between runs with the same version? IMHO, yes. This changes the signature of key generation from (Reader) to (Reader, internal random).
Huh, a change to spacing in gofmt? Won’t that cause extra source control churn in a lot of projects?
I like to think it's OK because gofmt reduces formatting churn overall.
In this specific case it doesn't do that: any time you add or remove a field from a structure, if it's one of the "extremes" gofmt might re-pad the entire thing, churning the entire structure.
I actually dislike the new heuristic as it becomes difficult to read `key: value` pair when value is, say, 20 white-spaces padded to the right. I remember having to turn on the "highlight current line" setting in my editor just to do that on some codebases.
Whether you like or dislike it, I don’t understand why it’s changing at all. I thought the main point of gofmt (which I applaud) was to avoid wasting any time whatsoever over formatting issues. It seems counterproductive for the golden formatting spec to be a moving target.
It actually could cause bigger problems than that. Most Go projects I've seen have CI runs that check whether 'gofmt' complains -- and fail builds if they do.

Ultimately though I don't really care all that much. Everyone will fix it in a few commands, and everything will be green again.

I care if it makes it harder to browse version history and see which changes were made when, and why.
I don't think it would make it much harder if people just do all of the 'gofmt' changes in a single commit. After all, it's not like your entire codebase consists of such edge-cases, and if you hit a 'git blame' where the commit is a format change you can always just do 'git blame $THE_FORMATTING_REVISION^ $file'.

I have to do this all the time when 'git blame -wM' doesn't give me what I want when looking at kernel changes.

This is why you lock to a specific version of gofmt and let team upgrade in conjuction with reformatted source repo.
The "prove pass" is a nice step forward. As that is improved, more subscript checks will be optimized out.
> Kernel calls on macOS and iOS now go through libSystem.so

Well it’s called libSystem.dylib :) but, still, good. It looks like this was only done in “runtime” but hopefully the rest can come in 1.12. [1]

[1] https://github.com/golang/go/issues/17490

If anyone wants the recording, this is the timestamp in the meetup recording before an edited version is available: https://youtu.be/RGeHsqj2RWo?t=2h16m11s

I can see that the slides alone aren't self-explanatory in some points, for example with the random byte reading or gofmt. I'll make sure to link to the issues/CLs in the slides next time :)

> Last release to support GOCACHE=off

That's a bit disturbing, since Go's test caching can be a bit of a nuisance. I did some quick Googling, but couldn't find an explanation.

If you want to disable test caching, you can still do; go test -count 1

if you want to rebuild without cache; go clean -cache && go build