joplin-notes/_resources/js-to-go.html
2026-11-04 00:33:23 +05:30

967 lines
38 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>JavaScript → Go — a phrasebook</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,300;12..96,400;12..96,500;12..96,700;12..96,800&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
<style>
:root{
/* palette drawn from the two languages' own identities */
--paper:#E7EDF1; /* cool godoc-ish paper */
--card:#FFFFFF;
--ink:#0B1215;
--ink-soft:#5A6B76;
--rule:#C9D5DD;
--go:#00ADD8; /* gopher blue */
--go-deep:#007D9C;
--js:#F7DF1E; /* js yellow */
--fuchsia:#CE3262; /* go brand fuchsia */
--teal:#00776F; /* go brand aqua, darkened for contrast */
--mono:"JetBrains Mono",ui-monospace,SFMono-Regular,Menlo,monospace;
--sans:"Bricolage Grotesque","Helvetica Neue",Arial,sans-serif;
}
*{box-sizing:border-box}
html{scroll-behavior:smooth;scroll-padding-top:88px}
body{
margin:0;
background:var(--paper);
color:var(--ink);
font-family:var(--sans);
font-size:17px;
line-height:1.5;
-webkit-font-smoothing:antialiased;
}
a{color:var(--go-deep)}
:focus-visible{outline:2.5px solid var(--fuchsia);outline-offset:3px;border-radius:2px}
.wrap{max-width:1180px;margin:0 auto;padding:0 24px}
/* ── sticky bar ─────────────────────────────── */
.bar{
position:sticky;top:0;z-index:50;
background:rgba(231,237,241,.88);
backdrop-filter:blur(10px);
border-bottom:1px solid var(--rule);
}
.bar-in{display:flex;align-items:center;gap:20px;height:64px}
.mark{
font-family:var(--mono);font-size:13px;font-weight:700;
letter-spacing:.08em;white-space:nowrap;text-decoration:none;color:var(--ink)
}
.mark em{font-style:normal;color:var(--fuchsia)}
.mark b{font-weight:700;color:var(--go-deep)}
.search{margin-left:auto;position:relative;display:flex;align-items:center}
.search input{
font-family:var(--mono);font-size:13px;
padding:9px 14px 9px 34px;width:260px;
border:1px solid var(--rule);border-radius:999px;
background:var(--card);color:var(--ink);
}
.search input::placeholder{color:#93A3AE}
.search svg{position:absolute;left:12px;width:14px;height:14px;stroke:var(--ink-soft);fill:none;stroke-width:2}
.count{font-family:var(--mono);font-size:12px;color:var(--ink-soft);white-space:nowrap}
/* ── hero ───────────────────────────────────── */
.hero{padding:88px 0 64px;border-bottom:1px solid var(--rule)}
.eyebrow{
font-family:var(--mono);font-size:12px;letter-spacing:.16em;
text-transform:uppercase;color:var(--ink-soft);margin:0 0 26px
}
.title{
font-family:var(--sans);
font-size:clamp(3rem,10vw,7.5rem);
font-weight:800;line-height:.86;letter-spacing:-.045em;
margin:0;
}
.title .arrow{color:var(--fuchsia);display:inline-block;padding:0 .06em}
.title .go{color:var(--go-deep)}
.deck{
max-width:38ch;margin:28px 0 0;font-size:1.15rem;color:var(--ink-soft);
font-weight:400;line-height:1.45
}
/* live rotating pair */
.demo{
margin-top:46px;background:var(--card);border:1px solid var(--rule);
border-radius:14px;overflow:hidden;max-width:820px;
box-shadow:0 1px 0 rgba(11,18,21,.04),0 14px 34px -26px rgba(11,18,21,.5)
}
.demo-row{display:grid;grid-template-columns:1fr 52px 1fr;align-items:stretch}
.demo-cell{padding:26px 22px;display:flex;align-items:center;min-height:96px}
.demo-cell code{font-family:var(--mono);font-size:.92rem;line-height:1.6;white-space:pre-wrap}
.demo-mid{
display:grid;place-items:center;border-left:1px solid var(--rule);
border-right:1px solid var(--rule);color:var(--go);font-size:1.5rem;
font-family:var(--mono);background:#F6F9FB
}
.demo-cell.js{border-top:3px solid var(--js)}
.demo-cell.go{border-top:3px solid var(--go)}
.fade{transition:opacity .45s ease}
.fade.out{opacity:0}
/* contents */
.contents{margin-top:52px}
.contents h2{
font-family:var(--mono);font-size:12px;letter-spacing:.16em;text-transform:uppercase;
color:var(--ink-soft);font-weight:500;margin:0 0 14px
}
.chips{display:flex;flex-wrap:wrap;gap:8px;padding:0;margin:0;list-style:none}
.chips a{
display:inline-block;font-family:var(--mono);font-size:12.5px;
padding:6px 12px;border:1px solid var(--rule);border-radius:999px;
background:var(--card);color:var(--ink);text-decoration:none;
transition:border-color .15s,color .15s
}
.chips a:hover{border-color:var(--go);color:var(--go-deep)}
</style>
</head>
<body>
<style>
/* ── entries ────────────────────────────────── */
.entries{padding:0 0 120px}
.entry{padding:56px 0;border-bottom:1px solid var(--rule)}
.entry[hidden]{display:none}
.entry-head{display:flex;align-items:baseline;gap:14px;flex-wrap:wrap;margin-bottom:22px}
.entry-head h3{
font-size:1.5rem;font-weight:700;letter-spacing:-.02em;margin:0;line-height:1.15
}
.entry-head .tag{
font-family:var(--mono);font-size:11px;letter-spacing:.12em;text-transform:uppercase;
color:var(--ink-soft);border:1px solid var(--rule);border-radius:999px;padding:3px 9px;
background:var(--card)
}
.pair{
display:grid;grid-template-columns:1fr 56px 1fr;align-items:stretch;
background:var(--card);border:1px solid var(--rule);border-radius:14px;overflow:hidden;
box-shadow:0 1px 0 rgba(11,18,21,.03)
}
.side{padding:20px 22px 22px;min-width:0}
.side.js{border-top:3px solid var(--js)}
.side.go{border-top:3px solid var(--go)}
.side .lang{
font-family:var(--mono);font-size:10.5px;letter-spacing:.16em;text-transform:uppercase;
color:var(--ink-soft);margin:0 0 12px;display:block
}
.side pre{margin:0;overflow-x:auto}
.side code{
font-family:var(--mono);font-size:.845rem;line-height:1.65;
white-space:pre;display:block;tab-size:4
}
.mid{
display:grid;place-items:center;background:#F6F9FB;
border-left:1px solid var(--rule);border-right:1px solid var(--rule);
color:var(--go);font-family:var(--mono);font-size:1.25rem;user-select:none
}
.note{
margin:18px 0 0;max-width:74ch;color:var(--ink-soft);font-size:1.02rem;line-height:1.55
}
.note b{color:var(--ink);font-weight:500}
.note code{
font-family:var(--mono);font-size:.86em;background:#DDE6EC;
padding:1px 5px;border-radius:4px;color:var(--ink)
}
/* token colours */
.tk{color:var(--fuchsia)} /* keyword */
.ts{color:var(--teal)} /* literal */
.tf{color:var(--go-deep)} /* call */
.tc{color:#8A97A3;font-style:italic} /* comment */
/* ── tooling table ──────────────────────────── */
.tooling{padding:56px 0 0}
.tooling h3{font-size:1.5rem;font-weight:700;letter-spacing:-.02em;margin:0 0 22px}
table{
width:100%;border-collapse:collapse;background:var(--card);
border:1px solid var(--rule);border-radius:14px;overflow:hidden;
font-family:var(--mono);font-size:.85rem
}
th{
text-align:left;padding:14px 20px;font-size:10.5px;letter-spacing:.16em;
text-transform:uppercase;color:var(--ink-soft);font-weight:500;
border-bottom:1px solid var(--rule)
}
th:first-child{box-shadow:inset 0 3px 0 var(--js)}
th:last-child{box-shadow:inset 0 3px 0 var(--go)}
td{padding:12px 20px;border-bottom:1px solid #EDF2F5;vertical-align:top}
tr:last-child td{border-bottom:none}
td:first-child{width:42%;color:var(--ink-soft)}
/* ── empty state ────────────────────────────── */
.empty{
display:none;padding:80px 0;text-align:center;font-size:1.1rem;color:var(--ink-soft)
}
.empty.on{display:block}
.empty span{font-family:var(--mono);color:var(--ink)}
footer{
padding:44px 0 80px;border-top:1px solid var(--rule);
font-family:var(--mono);font-size:12px;color:var(--ink-soft);line-height:1.8
}
/* ── responsive ─────────────────────────────── */
@media (max-width:880px){
.pair,.demo-row{grid-template-columns:1fr}
.mid,.demo-mid{
border-left:none;border-right:none;
border-top:1px solid var(--rule);border-bottom:1px solid var(--rule);
padding:8px 0
}
.mid span,.demo-mid span{display:inline-block;transform:rotate(90deg)}
.side.go,.demo-cell.go{border-top:none}
.search input{width:150px}
.deck{font-size:1.05rem}
}
@media (max-width:560px){
.bar-in{height:58px;gap:12px}
.count{display:none}
.hero{padding:56px 0 44px}
}
@media (prefers-reduced-motion:reduce){
html{scroll-behavior:auto}
.fade{transition:none}
}
</style>
<div class="bar">
<div class="wrap bar-in">
<a class="mark" href="#top">JS <em>&#8594;</em> <b>GO</b></a>
<span class="count" id="count"></span>
<div class="search">
<svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="M20 20l-4-4"/></svg>
<input id="filter" type="search" placeholder="find a construct" aria-label="Filter entries">
</div>
</div>
</div>
<main id="top">
<section class="hero">
<div class="wrap">
<p class="eyebrow">A phrasebook for JavaScript developers</p>
<h1 class="title">JavaScript<br><span class="arrow">&#8594;</span><span class="go">Go</span></h1>
<p class="deck">Look up something you already write every day, and read the Go sentence that means the same thing &mdash; plus a note on where the translation stops being literal.</p>
<div class="demo" aria-label="Example translations">
<div class="demo-row">
<div class="demo-cell js"><code id="demoJs" class="fade" data-lang="js"></code></div>
<div class="demo-mid"><span>&#8594;</span></div>
<div class="demo-cell go"><code id="demoGo" class="fade" data-lang="go"></code></div>
</div>
</div>
<nav class="contents" aria-label="Contents">
<h2>Contents</h2>
<ul class="chips" id="chips"></ul>
</nav>
</div>
</section>
<section class="entries wrap" id="entries">
<article class="entry" id="entry-point">
<div class="entry-head"><h3>Getting a program to run</h3><span class="tag">package main</span></div>
<div class="pair">
<div class="side js"><span class="lang">JavaScript</span><pre><code data-lang="js">// hello.js — run: node hello.js
console.log("Hello, world");
// any file can be the entry point,
// and top-level code just runs</code></pre></div>
<div class="mid"><span>&#8594;</span></div>
<div class="side go"><span class="lang">Go</span><pre><code data-lang="go">// hello.go — run: go run hello.go
package main
import "fmt"
func main() {
fmt.Println("Hello, world")
}</code></pre></div>
</div>
<p class="note">Every Go file belongs to a package. Only <code>package main</code> with a <code>func main()</code> builds into a runnable program. There is no top-level code &mdash; statements live inside functions.</p>
</article>
<article class="entry" id="variables">
<div class="entry-head"><h3>Declaring variables</h3><span class="tag">:= and var</span></div>
<div class="pair">
<div class="side js"><span class="lang">JavaScript</span><pre><code data-lang="js">let count = 1;
count = 2;
const name = "ada";
let empty; // undefined
var old = true; // avoid</code></pre></div>
<div class="mid"><span>&#8594;</span></div>
<div class="side go"><span class="lang">Go</span><pre><code data-lang="go">count := 1 // inside a function
count = 2
const name = "ada"
var empty string // "" — the zero value
var n int // 0
var ok bool // false</code></pre></div>
</div>
<p class="note"><code>:=</code> declares and infers the type in one step, but only inside a function; at package level use <code>var</code>. There is no <b>undefined</b> &mdash; every variable starts at its type's zero value. A declared local you never read is a compile error, not a warning.</p>
</article>
<article class="entry" id="types">
<div class="entry-head"><h3>Types and conversion</h3><span class="tag">strconv</span></div>
<div class="pair">
<div class="side js"><span class="lang">JavaScript</span><pre><code data-lang="js">typeof 42; // "number"
Number("42"); // 42
String(42); // "42"
parseInt("42", 10); // 42
(42).toFixed(2); // "42.00"
1 / 2; // 0.5</code></pre></div>
<div class="mid"><span>&#8594;</span></div>
<div class="side go"><span class="lang">Go</span><pre><code data-lang="go">reflect.TypeOf(42) // int
n, err := strconv.Atoi("42")
s := strconv.Itoa(42)
f, err := strconv.ParseFloat("4.2", 64)
fmt.Sprintf("%.2f", 42.0) // "42.00"
1 / 2 // 0 — integer division
float64(1) / 2 // 0.5</code></pre></div>
</div>
<p class="note">One <code>number</code> becomes <code>int</code>, <code>int64</code>, <code>float64</code> and friends. Conversion is always explicit &mdash; <code>float64(i)</code>, <code>int(f)</code> &mdash; and never happens behind your back, so mixing an <code>int</code> and a <code>float64</code> in one expression will not compile. <code>int(2.9)</code> truncates to 2. Parsing returns a value <b>and</b> an error.</p>
</article>
<article class="entry" id="strings">
<div class="entry-head"><h3>Working with strings</h3><span class="tag">strings, fmt</span></div>
<div class="pair">
<div class="side js"><span class="lang">JavaScript</span><pre><code data-lang="js">const who = "world";
`Hello, ${who}!`;
"abc".toUpperCase();
"a,b".split(",");
["a", "b"].join("-");
"abc".includes("b");
"abc".trim().replace("a", "z");
"héllo".length; // 5</code></pre></div>
<div class="mid"><span>&#8594;</span></div>
<div class="side go"><span class="lang">Go</span><pre><code data-lang="go">who := "world"
fmt.Sprintf("Hello, %s!", who)
strings.ToUpper("abc")
strings.Split("a,b", ",")
strings.Join([]string{"a", "b"}, "-")
strings.Contains("abc", "b")
strings.ReplaceAll(strings.TrimSpace(s), "a", "z")
len("héllo") // 6 bytes
utf8.RuneCountInString("héllo") // 5</code></pre></div>
</div>
<p class="note">Strings are immutable UTF-8 <b>bytes</b>. <code>len</code> counts bytes; ranging over a string yields runes (code points) with their byte offsets. There are no methods on strings &mdash; the verbs live in the <code>strings</code> package. Building a string in a loop wants <code>strings.Builder</code>, not <code>+=</code>.</p>
</article>
<article class="entry" id="lists">
<div class="entry-head"><h3>Lists</h3><span class="tag">slices</span></div>
<div class="pair">
<div class="side js"><span class="lang">JavaScript</span><pre><code data-lang="js">const xs = [1, 2, 3];
xs.push(4);
xs.length;
xs[0];
xs[1] = 9;
xs.slice(1, 3);
const ys = [...xs];
xs.concat(ys);</code></pre></div>
<div class="mid"><span>&#8594;</span></div>
<div class="side go"><span class="lang">Go</span><pre><code data-lang="go">xs := []int{1, 2, 3}
xs = append(xs, 4) // reassign!
len(xs)
xs[0]
xs[1] = 9
xs[1:3] // a view, shares memory
ys := make([]int, len(xs))
copy(ys, xs) // a real copy
append(xs, ys...)</code></pre></div>
</div>
<p class="note"><code>append</code> returns a new slice header, so you must assign the result back. Slicing does not copy: <code>xs[1:3]</code> looks into the same underlying array, and writing through it changes the original. <code>[3]int</code> is a fixed-size array and a genuinely different type from the slice <code>[]int</code>.</p>
</article>
<article class="entry" id="iteration">
<div class="entry-head"><h3>map, filter, reduce</h3><span class="tag">write the loop</span></div>
<div class="pair">
<div class="side js"><span class="lang">JavaScript</span><pre><code data-lang="js">xs.map(x =&gt; x * 2);
xs.filter(x =&gt; x &gt; 1);
xs.reduce((a, b) =&gt; a + b, 0);
xs.includes(2);
xs.indexOf(2);
xs.sort((a, b) =&gt; a - b);</code></pre></div>
<div class="mid"><span>&#8594;</span></div>
<div class="side go"><span class="lang">Go</span><pre><code data-lang="go">out := make([]int, 0, len(xs))
for _, x := range xs {
out = append(out, x*2)
}
sum := 0
for _, x := range xs {
sum += x
}
slices.Contains(xs, 2)
slices.Index(xs, 2)
slices.Sort(xs)</code></pre></div>
</div>
<p class="note">There is no built-in <code>map</code>/<code>filter</code>/<code>reduce</code>, and idiomatic Go writes the loop rather than reaching for a helper. What generics did add is the <code>slices</code> and <code>maps</code> packages, which cover sorting, searching, comparing and cloning.</p>
</article>
<article class="entry" id="maps">
<div class="entry-head"><h3>Key–value lookup</h3><span class="tag">map[K]V</span></div>
<div class="pair">
<div class="side js"><span class="lang">JavaScript</span><pre><code data-lang="js">const ages = { ada: 36 };
ages.ada;
ages["ada"];
ages.grace = 45;
delete ages.ada;
"ada" in ages;
Object.keys(ages);
const m = new Map();
m.set("k", 1);</code></pre></div>
<div class="mid"><span>&#8594;</span></div>
<div class="side go"><span class="lang">Go</span><pre><code data-lang="go">ages := map[string]int{"ada": 36}
ages["ada"]
v, ok := ages["ada"] // ok reports presence
ages["grace"] = 45
delete(ages, "ada")
len(ages)
m := make(map[string]int)
m["k"] = 1 // nil map would panic</code></pre></div>
</div>
<p class="note">Reading a missing key returns the <b>zero value</b>, not <code>undefined</code>, so <code>ages["nobody"]</code> is <code>0</code>. The comma-ok form is how you tell "absent" from "present and zero". Iteration order is deliberately randomised, and a <code>nil</code> map is fine to read but panics on write &mdash; <code>make</code> it first.</p>
</article>
<article class="entry" id="structs">
<div class="entry-head"><h3>Objects with a shape</h3><span class="tag">struct</span></div>
<div class="pair">
<div class="side js"><span class="lang">JavaScript</span><pre><code data-lang="js">const user = {
name: "ada",
age: 36,
};
user.name;
JSON.stringify(user);
const back = JSON.parse(text);</code></pre></div>
<div class="mid"><span>&#8594;</span></div>
<div class="side go"><span class="lang">Go</span><pre><code data-lang="go">type User struct {
Name string `json:"name"`
Age int `json:"age"`
}
u := User{Name: "ada", Age: 36}
u.Name
b, err := json.Marshal(u)
err = json.Unmarshal(text, &amp;back)</code></pre></div>
</div>
<p class="note">The struct is the everyday object: fixed fields, fixed types, checked at compile time. Tags in backticks control JSON names. A field is visible outside its package &mdash; and to <code>encoding/json</code> &mdash; only if it starts with a <b>capital letter</b>, which is the single most common reason a struct marshals to <code>{}</code>.</p>
</article>
<article class="entry" id="functions">
<div class="entry-head"><h3>Functions</h3><span class="tag">multiple returns</span></div>
<div class="pair">
<div class="side js"><span class="lang">JavaScript</span><pre><code data-lang="js">function add(a, b) { return a + b; }
const add2 = (a, b) =&gt; a + b;
function greet(name = "you") {}
function sum(...nums) {}
const { a, b } = pair();
const twice = f =&gt; x =&gt; f(f(x));</code></pre></div>
<div class="mid"><span>&#8594;</span></div>
<div class="side go"><span class="lang">Go</span><pre><code data-lang="go">func add(a, b int) int { return a + b }
add2 := func(a, b int) int { return a + b }
// no default arguments — use an options
// struct or a second constructor
func sum(nums ...int) int { ... }
a, b := pair() // two return values
func twice(f func(int) int) func(int) int { ... }</code></pre></div>
</div>
<p class="note">No default or named parameters; the usual answers are a config struct or variadic option functions. Returning several values is native, and that is how errors travel. <code>defer f.Close()</code> schedules cleanup to run when the function exits, whichever way it exits.</p>
</article>
<article class="entry" id="pointers">
<div class="entry-head"><h3>Copies and pointers</h3><span class="tag">&amp; and *</span></div>
<div class="pair">
<div class="side js"><span class="lang">JavaScript</span><pre><code data-lang="js">// the engine decides for you:
// objects by reference, primitives by value
function rename(u) { u.name = "new"; }
rename(user); // user is changed
let n = 1;
function inc(x) { x++; }
inc(n); // n is still 1</code></pre></div>
<div class="mid"><span>&#8594;</span></div>
<div class="side go"><span class="lang">Go</span><pre><code data-lang="go">// everything is passed by value.
// you choose when to pass an address.
func rename(u *User) { u.Name = "new" }
rename(&amp;u) // u is changed
func inc(x *int) { *x++ }
inc(&amp;n) // n is now 2
p := &amp;u
p.Name = "ada" // auto-dereferenced</code></pre></div>
</div>
<p class="note">A struct argument is a <b>copy</b> unless you pass a pointer, which you do when the function must mutate the original or the value is large. Slices, maps and channels already hold an internal reference, so they behave the way you expect. There is no pointer arithmetic and there is a garbage collector: this is only <code>&amp;</code> and <code>*</code>.</p>
</article>
<article class="entry" id="control-flow">
<div class="entry-head"><h3>Branches and loops</h3><span class="tag">one keyword: for</span></div>
<div class="pair">
<div class="side js"><span class="lang">JavaScript</span><pre><code data-lang="js">if (x &gt; 1) {} else if (y) {} else {}
for (let i = 0; i &lt; 10; i++) {}
while (ok) {}
do {} while (ok);
for (const x of xs) {}
for (const k in obj) {}
const size = x &gt; 1 ? "big" : "small";</code></pre></div>
<div class="mid"><span>&#8594;</span></div>
<div class="side go"><span class="lang">Go</span><pre><code data-lang="go">if x &gt; 1 { } else if y { } else { }
if v, err := f(); err != nil { } // scoped init
for i := 0; i &lt; 10; i++ { }
for ok { } // while
for { break } // infinite
for i, x := range xs { }
for k, v := range m { }
// no ternary — write the if</code></pre></div>
</div>
<p class="note"><code>for</code> is the only loop keyword and covers all four shapes. Conditions take no parentheses, braces are never optional, and the opening brace must sit on the same line. The scoped init form &mdash; <code>if v, err := f(); err != nil</code> &mdash; keeps <code>v</code> and <code>err</code> from leaking into the surrounding scope.</p>
</article>
<article class="entry" id="switch">
<div class="entry-head"><h3>switch</h3><span class="tag">no break needed</span></div>
<div class="pair">
<div class="side js"><span class="lang">JavaScript</span><pre><code data-lang="js">switch (x) {
case 1:
one();
break;
case 2:
case 3:
few();
break;
default:
other();
}</code></pre></div>
<div class="mid"><span>&#8594;</span></div>
<div class="side go"><span class="lang">Go</span><pre><code data-lang="go">switch x {
case 1:
one()
case 2, 3:
few()
default:
other()
}
switch { // no subject:
case x &gt; 10: // an if/else chain
big()
}</code></pre></div>
</div>
<p class="note">Cases break on their own; falling through is opt-in with the <code>fallthrough</code> keyword. One case can list several values, a case can carry an <code>init</code> statement, and a subjectless <code>switch</code> is the idiomatic replacement for a long if/else ladder.</p>
</article>
<article class="entry" id="equality">
<div class="entry-head"><h3>Equality and truthiness</h3><span class="tag">no coercion</span></div>
<div class="pair">
<div class="side js"><span class="lang">JavaScript</span><pre><code data-lang="js">1 == "1"; // true
1 === "1"; // false
if (list.length) {}
if (str) {}
if (!user) {}
value ?? fallback
user?.address?.city</code></pre></div>
<div class="mid"><span>&#8594;</span></div>
<div class="side go"><span class="lang">Go</span><pre><code data-lang="go">// == is always strict.
// comparing different types won't compile.
if len(list) &gt; 0 { }
if str != "" { }
if user == nil { }
// no ?? and no ?. — check it
if v, ok := m[k]; ok { use(v) }
if u != nil &amp;&amp; u.Address != nil { }</code></pre></div>
</div>
<p class="note">There is no truthiness: <code>if</code> takes a <code>bool</code> and nothing else, so every check is spelled out. Structs and arrays compare with <code>==</code> field by field; slices, maps and functions can only be compared against <code>nil</code>, and <code>reflect.DeepEqual</code> or <code>slices.Equal</code> handles the rest.</p>
</article>
<article class="entry" id="errors">
<div class="entry-head"><h3>Things going wrong</h3><span class="tag">errors are values</span></div>
<div class="pair">
<div class="side js"><span class="lang">JavaScript</span><pre><code data-lang="js">try {
const data = risky();
use(data);
} catch (err) {
console.error(err);
throw new Error("loading failed");
} finally {
cleanup();
}</code></pre></div>
<div class="mid"><span>&#8594;</span></div>
<div class="side go"><span class="lang">Go</span><pre><code data-lang="go">defer cleanup()
data, err := risky()
if err != nil {
return fmt.Errorf("loading: %w", err)
}
use(data)
errors.New("boom")
errors.Is(err, os.ErrNotExist)
panic("unrecoverable") // rare, not control flow</code></pre></div>
</div>
<p class="note">Nothing is thrown. An error is an ordinary value returned next to the result, and the <code>if err != nil</code> block you write every few lines <b>is</b> the idiom, not a sign you are doing it wrong. <code>%w</code> wraps an error so <code>errors.Is</code> and <code>errors.As</code> can look through the chain later.</p>
</article>
<article class="entry" id="classes">
<div class="entry-head"><h3>Classes</h3><span class="tag">methods and embedding</span></div>
<div class="pair">
<div class="side js"><span class="lang">JavaScript</span><pre><code data-lang="js">class Animal {
constructor(name) { this.name = name; }
speak() { return `${this.name} makes a sound`; }
}
class Dog extends Animal {
speak() { return `${this.name} barks`; }
}
new Dog("rex").speak();</code></pre></div>
<div class="mid"><span>&#8594;</span></div>
<div class="side go"><span class="lang">Go</span><pre><code data-lang="go">type Animal struct{ Name string }
func NewAnimal(name string) *Animal {
return &amp;Animal{Name: name}
}
func (a *Animal) Speak() string {
return a.Name + " makes a sound"
}
type Dog struct{ Animal } // embedding
func (d *Dog) Speak() string {
return d.Name + " barks"
}</code></pre></div>
</div>
<p class="note">No classes, no <code>new</code> keyword for your own types, no inheritance. A method is a function with a receiver, and the receiver replaces <code>this</code> &mdash; you name it yourself, and it is a pointer whenever the method mutates. Embedding promotes the inner type's fields and methods onto the outer one: composition, not a parent. A constructor is just a function called <code>NewDog</code>.</p>
</article>
<article class="entry" id="interfaces">
<div class="entry-head"><h3>Duck typing</h3><span class="tag">implicit interfaces</span></div>
<div class="pair">
<div class="side js"><span class="lang">JavaScript</span><pre><code data-lang="js">// anything with .speak() will do,
// and you find out at runtime
function speakAll(items) {
for (const i of items) {
console.log(i.speak());
}
}
if (x instanceof Dog) {}</code></pre></div>
<div class="mid"><span>&#8594;</span></div>
<div class="side go"><span class="lang">Go</span><pre><code data-lang="go">type Speaker interface {
Speak() string
}
func speakAll(items []Speaker) {
for _, i := range items {
fmt.Println(i.Speak())
}
}
if d, ok := x.(*Dog); ok { use(d) }</code></pre></div>
</div>
<p class="note">There is no <code>implements</code>. A type satisfies an interface simply by having the methods, which means you can write an interface for someone else's type after the fact. Keep them small &mdash; <code>io.Reader</code> is one method. <code>any</code> holds anything, and you get the concrete type back with an assertion or a <code>switch v := x.(type)</code>.</p>
</article>
<article class="entry" id="modules">
<div class="entry-head"><h3>Modules and visibility</h3><span class="tag">go.mod</span></div>
<div class="pair">
<div class="side js"><span class="lang">JavaScript</span><pre><code data-lang="js">// greet.js
export function hello() {}
export default thing;
// app.js
import { hello } from "./greet.js";
import thing from "some-pkg";
// package.json, node_modules/</code></pre></div>
<div class="mid"><span>&#8594;</span></div>
<div class="side go"><span class="lang">Go</span><pre><code data-lang="go">// greet/greet.go
package greet
func Hello() {} // exported: capital H
func local() {} // package-private
// main.go
import (
"fmt"
"example.com/app/greet"
)
// go.mod, shared module cache</code></pre></div>
</div>
<p class="note">Visibility is spelled with a capital letter rather than an <code>export</code> keyword, so renaming <code>hello</code> to <code>Hello</code> publishes it. A directory is a package and its import path is the module path plus that directory. Unused imports fail the build, which is why everyone runs <code>goimports</code> on save.</p>
</article>
<article class="entry" id="async">
<div class="entry-head"><h3>Doing things at the same time</h3><span class="tag">goroutines</span></div>
<div class="pair">
<div class="side js"><span class="lang">JavaScript</span><pre><code data-lang="js">async function load() {
const a = await fetchA();
return a;
}
const [x, y] = await Promise.all([p1(), p2()]);
setTimeout(fn, 1000);</code></pre></div>
<div class="mid"><span>&#8594;</span></div>
<div class="side go"><span class="lang">Go</span><pre><code data-lang="go">ch := make(chan string)
go func() { ch &lt;- fetchA() }()
a := &lt;-ch // blocks until ready
var wg sync.WaitGroup
wg.Add(2)
go func() { defer wg.Done(); p1() }()
go func() { defer wg.Done(); p2() }()
wg.Wait()
time.Sleep(time.Second)</code></pre></div>
</div>
<p class="note">No event loop, and no coloured functions: any ordinary function becomes concurrent by putting <code>go</code> in front of the call. Channels move values between goroutines, and an unbuffered send blocks until someone receives &mdash; that blocking is the synchronisation. If you would rather share memory, take a <code>sync.Mutex</code>. Nothing waits for your goroutines when <code>main</code> returns.</p>
</article>
<article class="entry" id="cancellation">
<div class="entry-head"><h3>Racing and cancelling</h3><span class="tag">select, context</span></div>
<div class="pair">
<div class="side js"><span class="lang">JavaScript</span><pre><code data-lang="js">await Promise.race([work(), timeout(1000)]);
const c = new AbortController();
fetch(url, { signal: c.signal });
c.abort();</code></pre></div>
<div class="mid"><span>&#8594;</span></div>
<div class="side go"><span class="lang">Go</span><pre><code data-lang="go">select {
case v := &lt;-work:
use(v)
case &lt;-time.After(time.Second):
return errTimeout
}
ctx, cancel := context.WithTimeout(ctx, time.Second)
defer cancel()
req = req.WithContext(ctx)</code></pre></div>
</div>
<p class="note"><code>select</code> waits on several channel operations and proceeds with whichever is ready first, so it covers both <code>Promise.race</code> and a non-blocking poll (with a <code>default</code> case). <code>context</code> carries deadlines and cancellation down a call chain and is, by convention, the first parameter of anything that blocks or touches the network.</p>
</article>
<article class="entry" id="tests">
<div class="entry-head"><h3>Tests</h3><span class="tag">go test</span></div>
<div class="pair">
<div class="side js"><span class="lang">JavaScript</span><pre><code data-lang="js">// add.test.js
import { test, expect } from "vitest";
test("adds", () =&gt; {
expect(add(1, 2)).toBe(3);
});
// npx vitest</code></pre></div>
<div class="mid"><span>&#8594;</span></div>
<div class="side go"><span class="lang">Go</span><pre><code data-lang="go">// add_test.go
package math
import "testing"
func TestAdd(t *testing.T) {
if got := add(1, 2); got != 3 {
t.Errorf("add(1, 2) = %d, want 3", got)
}
}
// go test ./...</code></pre></div>
</div>
<p class="note">The runner ships with the toolchain: no framework, no config, no dependency. Files end in <code>_test.go</code>, functions start with <code>Test</code> and take <code>*testing.T</code>. There are no assertion helpers on purpose &mdash; you compare values and call <code>t.Errorf</code> with a message that says what you got and what you wanted.</p>
</article>
</section>
<section class="wrap tooling" id="tooling">
<h3>The rest of the toolbox</h3>
<table>
<thead><tr><th>JavaScript</th><th>Go</th></tr></thead>
<tbody>
<tr><td>npm install</td><td>go get &middot; go mod tidy</td></tr>
<tr><td>package.json</td><td>go.mod</td></tr>
<tr><td>node_modules/</td><td>a shared module cache</td></tr>
<tr><td>npm run build</td><td>go build ./...</td></tr>
<tr><td>node app.js</td><td>go run .</td></tr>
<tr><td>prettier</td><td>gofmt — built in, and not configurable</td></tr>
<tr><td>eslint</td><td>go vet &middot; staticcheck</td></tr>
<tr><td>jest &middot; vitest</td><td>go test ./...</td></tr>
<tr><td>tsc</td><td>the compiler — types aren't optional</td></tr>
<tr><td>nodemon</td><td>air, or just rebuild; it's fast</td></tr>
<tr><td>JSDoc</td><td>go doc &middot; pkg.go.dev</td></tr>
<tr><td>esbuild + a Dockerfile</td><td>go build — one static binary</td></tr>
</tbody>
</table>
</section>
<p class="empty" id="empty">Nothing matches <span id="term"></span>. Try <span>append</span>, <span>goroutine</span> or <span>nil</span>.</p>
</main>
<footer class="wrap">
Written against Go 1.22 &middot; tabs are gofmt's doing, not mine<br>
Everything here compiles the way it reads, except where the note says otherwise.
</footer>
<script>
(function () {
"use strict";
/* ── minimal two-language highlighter ─────────────────────── */
var KW = {
js: /^(const|let|var|function|return|if|else|for|of|in|while|do|switch|case|default|break|continue|new|class|extends|this|try|catch|finally|throw|async|await|import|export|from|typeof|instanceof|delete|null|undefined|true|false)$/,
go: /^(package|import|func|var|const|type|struct|interface|map|chan|go|defer|range|select|fallthrough|return|if|else|for|switch|case|default|break|continue|nil|true|false|make|new|len|cap|append|copy|delete|panic|recover|string|int|int64|float64|bool|byte|rune|any|error)$/
};
var RE = /(\/\/[^\n]*|\/\*[\s\S]*?\*\/)|(`[^`]*`|"(?:\\.|[^"\\\n])*"|'(?:\\.|[^'\\\n])*')|(\b\d+(?:\.\d+)?\b)|([A-Za-z_$][\w$]*)/g;
function esc(s) {
return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}
function highlight(src, lang) {
var kw = KW[lang] || KW.js, out = "", last = 0, m;
RE.lastIndex = 0;
while ((m = RE.exec(src)) !== null) {
out += esc(src.slice(last, m.index));
var t = m[0];
if (m[1]) out += '<span class="tc">' + esc(t) + "</span>";
else if (m[2] || m[3]) out += '<span class="ts">' + esc(t) + "</span>";
else if (kw.test(t)) out += '<span class="tk">' + t + "</span>";
else if (src.charAt(m.index + t.length) === "(") out += '<span class="tf">' + t + "</span>";
else out += esc(t);
last = m.index + t.length;
}
return out + esc(src.slice(last));
}
function paint(el) {
el.innerHTML = highlight(el.textContent, el.getAttribute("data-lang"));
}
var blocks = document.querySelectorAll(".side code[data-lang]");
for (var i = 0; i < blocks.length; i++) paint(blocks[i]);
/* ── contents chips + search index ────────────────────────── */
var entries = [].slice.call(document.querySelectorAll(".entry"));
var chips = document.getElementById("chips");
entries.forEach(function (entry) {
var title = entry.querySelector("h3").textContent;
entry.dataset.search = (title + " " + entry.textContent).toLowerCase();
var li = document.createElement("li");
var a = document.createElement("a");
a.href = "#" + entry.id;
a.textContent = title.toLowerCase();
li.appendChild(a);
chips.appendChild(li);
});
var toolLi = document.createElement("li");
toolLi.innerHTML = '<a href="#tooling">the rest of the toolbox</a>';
chips.appendChild(toolLi);
/* ── filter ───────────────────────────────────────────────── */
var input = document.getElementById("filter");
var count = document.getElementById("count");
var empty = document.getElementById("empty");
var term = document.getElementById("term");
var tooling = document.getElementById("tooling");
function apply() {
var q = input.value.trim().toLowerCase();
var shown = 0;
entries.forEach(function (entry) {
var hit = !q || entry.dataset.search.indexOf(q) !== -1;
entry.hidden = !hit;
if (hit) shown++;
});
tooling.hidden = q.length > 0 && shown === 0;
empty.classList.toggle("on", shown === 0);
term.textContent = '"' + input.value.trim() + '"';
count.textContent = q ? shown + " of " + entries.length : entries.length + " constructs";
}
input.addEventListener("input", apply);
apply();
/* ── hero: one pair at a time ─────────────────────────────── */
var pairs = [
['const total = 1;', 'total := 1'],
['xs.push(4);', 'xs = append(xs, 4)'],
['try { risky() }\ncatch (err) { handle(err) }', 'v, err := risky()\nif err != nil { handle(err) }'],
['await Promise.all([p1(), p2()]);', 'wg.Wait()'],
['class Dog extends Animal {}', 'type Dog struct{ Animal }'],
['user?.address?.city', 'if u != nil && u.Address != nil {}'],
['xs.map(x => x * 2)', 'for _, x := range xs { ... }']
];
var jsCell = document.getElementById("demoJs");
var goCell = document.getElementById("demoGo");
var at = 0;
function show(n) {
jsCell.innerHTML = highlight(pairs[n][0], "js");
goCell.innerHTML = highlight(pairs[n][1], "go");
}
show(0);
var still = window.matchMedia("(prefers-reduced-motion: reduce)");
if (!still.matches) {
setInterval(function () {
jsCell.classList.add("out");
goCell.classList.add("out");
setTimeout(function () {
at = (at + 1) % pairs.length;
show(at);
jsCell.classList.remove("out");
goCell.classList.remove("out");
}, 460);
}, 3800);
}
})();
</script>
</body>
</html>