The Go programming language has become an important tool for developers, particularly around platforms like Kubernetes and Docker.
Go was created at Google by a team whose roots go back to Bell Labs and C. Their motivations included fast compilation time, and productive development of large scale distributed systems, handling high volumes of concurrent requests.
This article describes my experience as a new user of Go, building my first Go library. It follows a learning pattern similar to forays from Node to Rust.
The Tour of Go is a great way to get familiar with the language syntax. I started with 'hello world' at golang.org, and found myself going back to the tour for different topics.
The macOS installer copies everything into /usr/local/go
, so I opted to download the latest release from https://golang.org/dl/ into a versioned $GOROOT under my home directory. Here's what I have in my '.bash_profile':
export GOPATH=~/go
export GOROOT=~/go1.16.3
export PATH=${PATH}:${GOROOT}/bin:${GOPATH}/bin
The VS Code Go extension has improved a lot over the years. It now auto-installs the delve debugger, and the gopls language server. I did not have to do any additional configuration.
Hovering over types like Builder
shows source docs, and links to pkg.go.dev.
I found it quite easy to port shortscale-rs to shortscale-go.
Go has no ownership syntax, and the run-time includes a garbage collector.
In this case, I was also lucky that the Go strings.Builder standard library package is very similar to the writer pattern I ended up using for Rust.
Overall, I was pleasantly surprised with the readability of the code
package shortscale
import (
"strings"
)
// Shortscale converts numbers into English words.
// Supports positive integers from 0 to 999_999_999_999_999_999.
// Larger values return "(big number)".
func Shortscale(n uint64) string {
if n <= 20 {
return numwords[n]
}
if n > 999_999_999_999_999_999 {
return "(big number)"
}
b := new(strings.Builder)
writeScale(b, n, 1_000_000_000_000_000) // quadrillions
writeScale(b, n, 1_000_000_000_000) // trillions
writeScale(b, n, 1_000_000_000) // billions
writeScale(b, n, 1_000_000) // millions
writeScale(b, n, 1_000) // thousands
writeHundreds(b, n)
writeTensAndUnits(b, n, b.Len() > 0)
return b.String()
}
func writeTensAndUnits(b *strings.Builder, n uint64, ifAnd bool) {
n = n % 100
if n == 0 {
return
}
if ifAnd {
writeWord(b, "and")
}
if n <= 20 {
writeWord(b, numwords[n])
return
}
writeWord(b, numwords[n/10*10]) // tens
units := n % 10
if units > 0 {
writeWord(b, numwords[units])
}
}
func writeHundreds(b *strings.Builder, n uint64) {
n = n / 100 % 10
if n == 0 {
return
}
writeWord(b, numwords[n])
writeWord(b, numwords[100])
}
func writeScale(b *strings.Builder, n uint64, thousands uint64) {
n = n / thousands % 1_000
if n == 0 {
return
}
writeHundreds(b, n)
writeTensAndUnits(b, n, (n/100%10) > 0)
writeWord(b, numwords[thousands])
}
func writeWord(b *strings.Builder, word string) {
if b.Len() > 0 {
b.WriteString(" ")
}
b.WriteString(word)
}
var numwords = map[uint64]string{
0: "zero",
1: "one",
2: "two",
3: "three",
4: "four",
5: "five",
6: "six",
7: "seven",
8: "eight",
9: "nine",
10: "ten",
11: "eleven",
12: "twelve",
13: "thirteen",
14: "fourteen",
15: "fifteen",
16: "sixteen",
17: "seventeen",
18: "eighteen",
19: "nineteen",
20: "twenty",
30: "thirty",
40: "forty",
50: "fifty",
60: "sixty",
70: "seventy",
80: "eighty",
90: "ninety",
100: "hundred",
1_000: "thousand",
1_000_000: "million",
1_000_000_000: "billion",
1_000_000_000_000: "trillion",
1_000_000_000_000_000: "quadrillion",
}
The testing package provides support for running tests and benchmarks with go test
. The GitHub Action workflow for shortscale-go make use of this.
Out of curiosity, I ran BenchmarkShortscale for two variants of the Shortscale function, one which pre-allocates memory for string.Builder, and one which does not. Pre-allocating, reduced the number of allocs/op from 4 to 1, improving ns/op by about 85ns.
Pre-allocated
$ go test -bench . -benchmem
goos: darwin
goarch: amd64
pkg: github.com/jldec/shortscale-go
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
5694252 205.5 ns/op 64 B/op 1 allocs/op
Not pre-allocated
$ go test -bench . -benchmem
goos: darwin
goarch: amd64
pkg: github.com/jldec/shortscale-go
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
4100697 292.9 ns/op 120 B/op 4 allocs/op
Until quite recently, Go did not have built-in package versioning like npm or cargo. This led to incompatibile versioning add-ons, like godep and glide, which made packages with nested dependencies difficult to consume. E.g. see this old INSTALL.md from kubernetes/client-go.
Fortunately, Go modules are enabled as the default in Go since v1.16.
I created my shortscale-go module with go mod init following the guide in Using Go Modules.
$ go mod init github.com/jldec/shortscale-go
go: creating new go.mod: module github.com/jldec/shortscale-go
This created a new go.mod file with the following content.
module github.com/jldec/shortscale-go
go 1.16
I was a little surprised that there was no way to indicate the module version inside go.mod
. Go relies on git tags in the form vx.x.x for this. As I pushed each version to GitHub, I used the GitHub Releases UI to create the tag.
The shortscale package is published at https://pkg.go.dev/github.com/jldec/shortscale-go
It turns out that fetching any versioned module with go get
, automatically adds that module to the registry at go.dev. This feels a little strange at first, but the more I use it, the more I think it's a clever solution.
How about using a similar scheme to create a vendor-neutral registry for ESM modules?
š¤
To leave a comment
please visit dev.to/jldec
{ "path": "/blog/getting-started-with-go", "attrs": { "title": "Getting started with Go", "splash": { "image": "/images/church-blossoms.jpg" }, "date": "2021-04-03", "layout": "BlogPostLayout", "excerpt": "The [Go](https://go.dev/) programming language has become an important tool for developers, particularly around platforms like Kubernetes and Docker.\n" }, "md": "# Getting started with Go\n\nThe [Go](https://go.dev/) programming language has become an important tool for developers, particularly around platforms like Kubernetes and Docker.\n\nGo was created at Google by a team whose roots go back to Bell Labs and [C](https://en.wikipedia.org/wiki/C_(programming_language)). Their [motivations](https://talks.golang.org/2012/splash.article) included fast compilation time, and productive development of large scale distributed systems, handling high volumes of concurrent requests.\n\nThis article describes my experience as a new user of Go, building my first Go library. It follows a learning pattern similar to [forays from Node to Rust](forays-from-node-to-rust).\n\n## Getting started\n\nThe [Tour of Go](https://tour.golang.org/basics/1) is a great way to get familiar with the language syntax. I started with 'hello world' at [golang.org](https://golang.org/doc/tutorial/getting-started#install), and found myself going back to the tour for different topics.\n\n[![Screenshot of the Go Tour showing code and navigation](/images/go-tour.png \".no-border\")](https://tour.golang.org/basics/1)\n\nThe macOS [installer](https://golang.org/doc/manage-install) copies everything into `/usr/local/go`, so I opted to download the latest release from https://golang.org/dl/ into a versioned [$GOROOT](https://golang.org/doc/install/source#environment) under my home directory. Here's what I have in my '.bash_profile':\n\n```sh\nexport GOPATH=~/go\nexport GOROOT=~/go1.16.3\nexport PATH=${PATH}:${GOROOT}/bin:${GOPATH}/bin\n```\n\n## VS Code\n\nThe [VS Code Go](https://github.com/golang/vscode-go/) extension has improved a lot over the years. It now auto-installs the [delve](https://github.com/go-delve/delve) debugger, and the [gopls](https://blog.golang.org/gopls-vscode-go) language server. I did not have to do any additional configuration.\n\nHovering over types like `Builder` shows source docs, and links to [pkg.go.dev](https://go.dev).\n\n![VS Code screenshot showing hover over strings.Builder](/images/go-vs-code.png)\n\n## Porting from Rust to Go\n\nI found it quite easy to port [shortscale-rs](https://github.com/jldec/shortscale-rs/blob/main/src/shortscale.rs#L15) to [shortscale-go](https://github.com/jldec/shortscale-go/blob/main/shortscale.go).\n\nGo has no ownership syntax, and the run-time includes a garbage collector.\n\nIn this case, I was also lucky that the Go [strings.Builder](https://pkg.go.dev/strings#Builder) standard library package is very similar to the [writer](https://github.com/jldec/shortscale-rs/blob/main/src/shortscale.rs#L46) pattern I ended up using for Rust.\n\n> Overall, I was pleasantly surprised with the readability of the code\n\n```go\npackage shortscale\n\nimport (\n \"strings\"\n)\n\n// Shortscale converts numbers into English words.\n// Supports positive integers from 0 to 999_999_999_999_999_999.\n// Larger values return \"(big number)\".\nfunc Shortscale(n uint64) string {\n if n <= 20 {\n return numwords[n]\n }\n if n > 999_999_999_999_999_999 {\n return \"(big number)\"\n }\n b := new(strings.Builder)\n writeScale(b, n, 1_000_000_000_000_000) // quadrillions\n writeScale(b, n, 1_000_000_000_000) // trillions\n writeScale(b, n, 1_000_000_000) // billions\n writeScale(b, n, 1_000_000) // millions\n writeScale(b, n, 1_000) // thousands\n writeHundreds(b, n)\n writeTensAndUnits(b, n, b.Len() > 0)\n return b.String()\n}\n\nfunc writeTensAndUnits(b *strings.Builder, n uint64, ifAnd bool) {\n n = n % 100\n if n == 0 {\n return\n }\n if ifAnd {\n writeWord(b, \"and\")\n }\n if n <= 20 {\n writeWord(b, numwords[n])\n return\n }\n writeWord(b, numwords[n/10*10]) // tens\n units := n % 10\n if units > 0 {\n writeWord(b, numwords[units])\n }\n}\n\nfunc writeHundreds(b *strings.Builder, n uint64) {\n n = n / 100 % 10\n if n == 0 {\n return\n }\n writeWord(b, numwords[n])\n writeWord(b, numwords[100])\n}\n\nfunc writeScale(b *strings.Builder, n uint64, thousands uint64) {\n n = n / thousands % 1_000\n if n == 0 {\n return\n }\n writeHundreds(b, n)\n writeTensAndUnits(b, n, (n/100%10) > 0)\n writeWord(b, numwords[thousands])\n}\n\nfunc writeWord(b *strings.Builder, word string) {\n if b.Len() > 0 {\n b.WriteString(\" \")\n }\n b.WriteString(word)\n}\n\nvar numwords = map[uint64]string{\n 0: \"zero\",\n 1: \"one\",\n 2: \"two\",\n 3: \"three\",\n 4: \"four\",\n 5: \"five\",\n 6: \"six\",\n 7: \"seven\",\n 8: \"eight\",\n 9: \"nine\",\n 10: \"ten\",\n 11: \"eleven\",\n 12: \"twelve\",\n 13: \"thirteen\",\n 14: \"fourteen\",\n 15: \"fifteen\",\n 16: \"sixteen\",\n 17: \"seventeen\",\n 18: \"eighteen\",\n 19: \"nineteen\",\n 20: \"twenty\",\n 30: \"thirty\",\n 40: \"forty\",\n 50: \"fifty\",\n 60: \"sixty\",\n 70: \"seventy\",\n 80: \"eighty\",\n 90: \"ninety\",\n 100: \"hundred\",\n 1_000: \"thousand\",\n 1_000_000: \"million\",\n 1_000_000_000: \"billion\",\n 1_000_000_000_000: \"trillion\",\n 1_000_000_000_000_000: \"quadrillion\",\n}\n```\n\n## Tests and benchmarks\n\nThe [testing](https://pkg.go.dev/testing) package provides support for running tests and benchmarks with `go test`. The GitHub Action [workflow](https://github.com/jldec/shortscale-go/blob/main/.github/workflows/ci.yaml#L25) for shortscale-go make use of this.\n\nOut of curiosity, I ran [BenchmarkShortscale](https://github.com/jldec/shortscale-go/blob/main/shortscale_test.go#L24) for two variants of the Shortscale function, one which [pre-allocates](https://github.com/jldec/shortscale-go/blob/358a49f24dcb9d4b2c697233f37f5dea4c87d318/shortscale.go#L18) memory for string.Builder, and one which does not. Pre-allocating, reduced the number of allocs/op from 4 to 1, improving ns/op by about 85ns.\n\n**Pre-allocated**\n```\n$ go test -bench . -benchmem\ngoos: darwin\ngoarch: amd64\npkg: github.com/jldec/shortscale-go\ncpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz\n\n5694252\t 205.5 ns/op\t 64 B/op\t 1 allocs/op\n```\n\n**Not pre-allocated**\n```\n$ go test -bench . -benchmem\ngoos: darwin\ngoarch: amd64\npkg: github.com/jldec/shortscale-go\ncpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz\n\n4100697\t 292.9 ns/op\t 120 B/op\t 4 allocs/op\n```\n\n\n## Dependency management\n\nUntil quite recently, Go [did not have](https://research.swtch.com/vgo-intro#versioning_and_api_stability) built-in package versioning like [npm](migrating-from-cjs-to-esm) or [cargo](forays-from-node-to-rust). This led to incompatibile versioning add-ons, like [godep](https://github.com/tools/godep) and [glide](https://github.com/Masterminds/glide), which made packages with nested dependencies difficult to consume. E.g. see this old [INSTALL.md](https://github.com/kubernetes/client-go/blob/416948da08dfd61cd4a08a3d679865ce91ff39b6/INSTALL.md#dependency-management-for-the-serious-or-reluctant-user) from kubernetes/client-go.\n\n> Fortunately, [Go modules](https://blog.golang.org/using-go-modules) are enabled as the default in Go since [v1.16](https://blog.golang.org/go116-module-changes).\n\n## go.mod\n\nI created my [shortscale-go](https://github.com/jldec/shortscale-go/blob/main/go.mod) module with [go mod init](https://golang.org/pkg/cmd/go/#hdr-Module_maintenance) following the guide in [Using Go Modules](https://blog.golang.org/using-go-modules).\n\n```sh\n$ go mod init github.com/jldec/shortscale-go\ngo: creating new go.mod: module github.com/jldec/shortscale-go\n```\nThis created a new **go.mod** file with the following content.\n\n```js\nmodule github.com/jldec/shortscale-go\n\ngo 1.16\n```\n\nI was a little surprised that there was no way to indicate the module version inside `go.mod`. Go [relies on git tags](https://blog.golang.org/publishing-go-modules) in the form vx.x.x for this. As I pushed each version to GitHub, I used the GitHub [Releases](https://github.com/jldec/shortscale-go/releases) UI to create the tag.\n\n## pkg.go.dev\n\nThe shortscale package is published at https://pkg.go.dev/github.com/jldec/shortscale-go\n\n[![Module page for shortscale-go on go.dev](/images/shortscale-go-go-dev.png)](https://pkg.go.dev/github.com/jldec/shortscale-go)\n\nIt turns out that fetching any versioned module with `go get`, automatically adds that module to the registry at [go.dev](https://go.dev/about). This feels a little strange at first, but the more I use it, the more I think it's a clever solution.\n\n> How about using a similar scheme to create a vendor-neutral registry for [ESM modules](extracting-an-esm-module-from-a-deno-script)? \nš¤\n\n_To leave a comment \nplease visit [dev.to/jldec](https://dev.to/jldec/getting-started-with-go-2m9e)_\n\n\n", "html": "<h1>Getting started with Go</h1>\n<p>The <a href=\"https://go.dev/\">Go</a> programming language has become an important tool for developers, particularly around platforms like Kubernetes and Docker.</p>\n<p>Go was created at Google by a team whose roots go back to Bell Labs and <a href=\"https://en.wikipedia.org/wiki/C_(programming_language)\">C</a>. Their <a href=\"https://talks.golang.org/2012/splash.article\">motivations</a> included fast compilation time, and productive development of large scale distributed systems, handling high volumes of concurrent requests.</p>\n<p>This article describes my experience as a new user of Go, building my first Go library. It follows a learning pattern similar to <a href=\"forays-from-node-to-rust\">forays from Node to Rust</a>.</p>\n<h2>Getting started</h2>\n<p>The <a href=\"https://tour.golang.org/basics/1\">Tour of Go</a> is a great way to get familiar with the language syntax. I started with 'hello world' at <a href=\"https://golang.org/doc/tutorial/getting-started#install\">golang.org</a>, and found myself going back to the tour for different topics.</p>\n<p><a href=\"https://tour.golang.org/basics/1\"><img src=\"/images/go-tour.png\" alt=\"Screenshot of the Go Tour showing code and navigation\" title=\".no-border\"></a></p>\n<p>The macOS <a href=\"https://golang.org/doc/manage-install\">installer</a> copies everything into <code>/usr/local/go</code>, so I opted to download the latest release from <a href=\"https://golang.org/dl/\">https://golang.org/dl/</a> into a versioned <a href=\"https://golang.org/doc/install/source#environment\">$GOROOT</a> under my home directory. Here's what I have in my '.bash_profile':</p>\n<pre><code class=\"language-sh\">export GOPATH=~/go\nexport GOROOT=~/go1.16.3\nexport PATH=${PATH}:${GOROOT}/bin:${GOPATH}/bin\n</code></pre>\n<h2>VS Code</h2>\n<p>The <a href=\"https://github.com/golang/vscode-go/\">VS Code Go</a> extension has improved a lot over the years. It now auto-installs the <a href=\"https://github.com/go-delve/delve\">delve</a> debugger, and the <a href=\"https://blog.golang.org/gopls-vscode-go\">gopls</a> language server. I did not have to do any additional configuration.</p>\n<p>Hovering over types like <code>Builder</code> shows source docs, and links to <a href=\"https://go.dev\">pkg.go.dev</a>.</p>\n<p><img src=\"/images/go-vs-code.png\" alt=\"VS Code screenshot showing hover over strings.Builder\"></p>\n<h2>Porting from Rust to Go</h2>\n<p>I found it quite easy to port <a href=\"https://github.com/jldec/shortscale-rs/blob/main/src/shortscale.rs#L15\">shortscale-rs</a> to <a href=\"https://github.com/jldec/shortscale-go/blob/main/shortscale.go\">shortscale-go</a>.</p>\n<p>Go has no ownership syntax, and the run-time includes a garbage collector.</p>\n<p>In this case, I was also lucky that the Go <a href=\"https://pkg.go.dev/strings#Builder\">strings.Builder</a> standard library package is very similar to the <a href=\"https://github.com/jldec/shortscale-rs/blob/main/src/shortscale.rs#L46\">writer</a> pattern I ended up using for Rust.</p>\n<blockquote>\n<p>Overall, I was pleasantly surprised with the readability of the code</p>\n</blockquote>\n<pre><code class=\"language-go\">package shortscale\n\nimport (\n "strings"\n)\n\n// Shortscale converts numbers into English words.\n// Supports positive integers from 0 to 999_999_999_999_999_999.\n// Larger values return "(big number)".\nfunc Shortscale(n uint64) string {\n if n <= 20 {\n return numwords[n]\n }\n if n > 999_999_999_999_999_999 {\n return "(big number)"\n }\n b := new(strings.Builder)\n writeScale(b, n, 1_000_000_000_000_000) // quadrillions\n writeScale(b, n, 1_000_000_000_000) // trillions\n writeScale(b, n, 1_000_000_000) // billions\n writeScale(b, n, 1_000_000) // millions\n writeScale(b, n, 1_000) // thousands\n writeHundreds(b, n)\n writeTensAndUnits(b, n, b.Len() > 0)\n return b.String()\n}\n\nfunc writeTensAndUnits(b *strings.Builder, n uint64, ifAnd bool) {\n n = n % 100\n if n == 0 {\n return\n }\n if ifAnd {\n writeWord(b, "and")\n }\n if n <= 20 {\n writeWord(b, numwords[n])\n return\n }\n writeWord(b, numwords[n/10*10]) // tens\n units := n % 10\n if units > 0 {\n writeWord(b, numwords[units])\n }\n}\n\nfunc writeHundreds(b *strings.Builder, n uint64) {\n n = n / 100 % 10\n if n == 0 {\n return\n }\n writeWord(b, numwords[n])\n writeWord(b, numwords[100])\n}\n\nfunc writeScale(b *strings.Builder, n uint64, thousands uint64) {\n n = n / thousands % 1_000\n if n == 0 {\n return\n }\n writeHundreds(b, n)\n writeTensAndUnits(b, n, (n/100%10) > 0)\n writeWord(b, numwords[thousands])\n}\n\nfunc writeWord(b *strings.Builder, word string) {\n if b.Len() > 0 {\n b.WriteString(" ")\n }\n b.WriteString(word)\n}\n\nvar numwords = map[uint64]string{\n 0: "zero",\n 1: "one",\n 2: "two",\n 3: "three",\n 4: "four",\n 5: "five",\n 6: "six",\n 7: "seven",\n 8: "eight",\n 9: "nine",\n 10: "ten",\n 11: "eleven",\n 12: "twelve",\n 13: "thirteen",\n 14: "fourteen",\n 15: "fifteen",\n 16: "sixteen",\n 17: "seventeen",\n 18: "eighteen",\n 19: "nineteen",\n 20: "twenty",\n 30: "thirty",\n 40: "forty",\n 50: "fifty",\n 60: "sixty",\n 70: "seventy",\n 80: "eighty",\n 90: "ninety",\n 100: "hundred",\n 1_000: "thousand",\n 1_000_000: "million",\n 1_000_000_000: "billion",\n 1_000_000_000_000: "trillion",\n 1_000_000_000_000_000: "quadrillion",\n}\n</code></pre>\n<h2>Tests and benchmarks</h2>\n<p>The <a href=\"https://pkg.go.dev/testing\">testing</a> package provides support for running tests and benchmarks with <code>go test</code>. The GitHub Action <a href=\"https://github.com/jldec/shortscale-go/blob/main/.github/workflows/ci.yaml#L25\">workflow</a> for shortscale-go make use of this.</p>\n<p>Out of curiosity, I ran <a href=\"https://github.com/jldec/shortscale-go/blob/main/shortscale_test.go#L24\">BenchmarkShortscale</a> for two variants of the Shortscale function, one which <a href=\"https://github.com/jldec/shortscale-go/blob/358a49f24dcb9d4b2c697233f37f5dea4c87d318/shortscale.go#L18\">pre-allocates</a> memory for string.Builder, and one which does not. Pre-allocating, reduced the number of allocs/op from 4 to 1, improving ns/op by about 85ns.</p>\n<p><strong>Pre-allocated</strong></p>\n<pre><code>$ go test -bench . -benchmem\ngoos: darwin\ngoarch: amd64\npkg: github.com/jldec/shortscale-go\ncpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz\n\n5694252\t 205.5 ns/op\t 64 B/op\t 1 allocs/op\n</code></pre>\n<p><strong>Not pre-allocated</strong></p>\n<pre><code>$ go test -bench . -benchmem\ngoos: darwin\ngoarch: amd64\npkg: github.com/jldec/shortscale-go\ncpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz\n\n4100697\t 292.9 ns/op\t 120 B/op\t 4 allocs/op\n</code></pre>\n<h2>Dependency management</h2>\n<p>Until quite recently, Go <a href=\"https://research.swtch.com/vgo-intro#versioning_and_api_stability\">did not have</a> built-in package versioning like <a href=\"migrating-from-cjs-to-esm\">npm</a> or <a href=\"forays-from-node-to-rust\">cargo</a>. This led to incompatibile versioning add-ons, like <a href=\"https://github.com/tools/godep\">godep</a> and <a href=\"https://github.com/Masterminds/glide\">glide</a>, which made packages with nested dependencies difficult to consume. E.g. see this old <a href=\"https://github.com/kubernetes/client-go/blob/416948da08dfd61cd4a08a3d679865ce91ff39b6/INSTALL.md#dependency-management-for-the-serious-or-reluctant-user\">INSTALL.md</a> from kubernetes/client-go.</p>\n<blockquote>\n<p>Fortunately, <a href=\"https://blog.golang.org/using-go-modules\">Go modules</a> are enabled as the default in Go since <a href=\"https://blog.golang.org/go116-module-changes\">v1.16</a>.</p>\n</blockquote>\n<h2>go.mod</h2>\n<p>I created my <a href=\"https://github.com/jldec/shortscale-go/blob/main/go.mod\">shortscale-go</a> module with <a href=\"https://golang.org/pkg/cmd/go/#hdr-Module_maintenance\">go mod init</a> following the guide in <a href=\"https://blog.golang.org/using-go-modules\">Using Go Modules</a>.</p>\n<pre><code class=\"language-sh\">$ go mod init github.com/jldec/shortscale-go\ngo: creating new go.mod: module github.com/jldec/shortscale-go\n</code></pre>\n<p>This created a new <strong>go.mod</strong> file with the following content.</p>\n<pre><code class=\"language-js\">module github.com/jldec/shortscale-go\n\ngo 1.16\n</code></pre>\n<p>I was a little surprised that there was no way to indicate the module version inside <code>go.mod</code>. Go <a href=\"https://blog.golang.org/publishing-go-modules\">relies on git tags</a> in the form vx.x.x for this. As I pushed each version to GitHub, I used the GitHub <a href=\"https://github.com/jldec/shortscale-go/releases\">Releases</a> UI to create the tag.</p>\n<h2>pkg.go.dev</h2>\n<p>The shortscale package is published at <a href=\"https://pkg.go.dev/github.com/jldec/shortscale-go\">https://pkg.go.dev/github.com/jldec/shortscale-go</a></p>\n<p><a href=\"https://pkg.go.dev/github.com/jldec/shortscale-go\"><img src=\"/images/shortscale-go-go-dev.png\" alt=\"Module page for shortscale-go on go.dev\"></a></p>\n<p>It turns out that fetching any versioned module with <code>go get</code>, automatically adds that module to the registry at <a href=\"https://go.dev/about\">go.dev</a>. This feels a little strange at first, but the more I use it, the more I think it's a clever solution.</p>\n<blockquote>\n<p>How about using a similar scheme to create a vendor-neutral registry for <a href=\"extracting-an-esm-module-from-a-deno-script\">ESM modules</a>?<br>\nš¤</p>\n</blockquote>\n<p><em>To leave a comment<br>\nplease visit <a href=\"https://dev.to/jldec/getting-started-with-go-2m9e\">dev.to/jldec</a></em></p>\n" }