跪拜 Guibai
← Back to the summary

Go's Strict Type System Hits a JavaScript Developer: Variables, Zero Values, and the Cost of Implicit Conversion

This is the second article in the "Frontend Developer Transitioning to Go Full-Stack" series.

Previous article: Frontend Developer Transitioning to Go Full-Stack (1): Is Frontend Alone Enough for the Future?

The previous article completed the installation and verification of the Go development environment. This article will formally create the first Go project and learn from variable declaration and scope all the way to type conversion.

Foreword: The environment is set up, finally time to start coding

In the previous article, I mainly completed these tasks:

Why transition from frontend to full-stack
→ Why choose Go
→ Install Go
→ Verify Go version
→ Understand GOROOT and GOPATH
→ Determine the subsequent learning path

At that time, running:

go version

The terminal successfully output:

go version go1.26.5 windows/amd64

At this point, Go was officially installed.

But strictly speaking, the previous article hadn't really started writing Go yet.

No project was created, no main.go was written, and I hadn't experienced the Go compiler's "temperament."

So in this article, I can finally set aside the environment configuration and formally type my first line of Go code.

I originally thought the introductory phase would be relatively easy.

But starting from variable declaration, the Go compiler kept reminding me:

This variable is not used
That package is not used
Types are different and cannot be calculated
:= cannot be used here

As a frontend developer who has long used JavaScript and TypeScript, I gradually discovered:

Go's syntax isn't necessarily difficult, but it really likes to make the rules clear upfront.

This article will record, from the first Hello, Go! to type conversion, exactly how many times I was reminded by the Go compiler.


Creating the First Go Project

My Go learning directory is:

D:\LearningGO

This time, I created the first project inside it.

Since I am using Git Bash, I executed:

cd /d/LearningGO
mkdir 01-hello-go
cd 01-hello-go

Then initialized the Go Module:

go mod init hello-go

After successful execution, the terminal output:

go: creating new go.mod: module hello-go

A new file was also generated in the project:

go.mod

The current directory structure is:

01-hello-go/
└── go.mod

What is go.mod?

At this stage, go.mod can be understood as the module configuration file for the current Go project.

Opening it, you can see content similar to the following:

module hello-go

go 1.26

Where:

module hello-go

declares the path of the current module.

As a frontend developer, you can temporarily make an imprecise analogy:

go.mod          ≈ package.json
module hello-go ≈ the name field in package.json

The reason this analogy is not entirely accurate is that a Go Module does more than just record a project name.

Later, when importing self-written packages or managing third-party dependencies in the project, it will all relate to the module path declared here.

In formal projects, the module path is usually written as a code repository address, for example:

github.com/username/project-name

But for now, since it's just local practice, I'll use:

hello-go

At this stage, just remember one thing:

When creating a new Go project, you usually need to execute go mod init first to initialize the current module.


Writing the First Hello Go

Next, I created main.go in the project:

package main

import "fmt"

func main() {
	fmt.Println("Hello, Go!")
}

The project structure now became:

01-hello-go/
├── go.mod
└── main.go

Then executed:

go run .

Terminal output:

Hello, Go!

The first piece of Go code ran successfully.

Although it only output one sentence, these few lines of code already contain several concepts that might be unfamiliar to frontend developers:

package main
import "fmt"
func main()

Let's get to know them one by one.


package main: Declares which package the current file belongs to

The first line of code is:

package main

Every code file in Go must belong to a certain package.

main is a special package name. An executable program typically needs to use package main and contain a main function that takes no parameters and returns no value.

For now, you can simply understand it as:

package main
+
func main()
=
This is a directly runnable Go program

When learning code splitting later, we will also create regular packages. Then we'll further understand how Go organizes code through packages.


import "fmt": Importing the Standard Library

The second part is:

import "fmt"

This line imports the fmt package from Go's standard library.

fmt is mainly used to handle formatted input and output.

For example:

fmt.Println("Hello, Go!")

From a frontend perspective, its role is somewhat similar to this code:

console.log("Hello, Go!");

They are certainly not exactly the same thing, but both can be used to output content to the terminal.

Here:

fmt.Println

can be temporarily broken down and understood as:

fmt      the imported package
Println  a function provided by the fmt package

func main(): The program starts execution from here

Next is:

func main() {
}

func is used to declare a function.

main is the function name, the following () is used to define the parameter list, and {} contains the function body.

In the main package, main() is the entry point of the program. When we run this Go program, the code starts executing from here.

To give the first piece of code a bit more of a "kick-off ceremony" feel, I added another line:

package main

import "fmt"

func main() {
	fmt.Println("Hello, Go!")
	fmt.Println("I am a frontend developer, learning Go full-stack development.")
}

Run again:

go run .

Output:

Hello, Go!
I am a frontend developer, learning Go full-stack development.

At this point, the first Go program was truly up and running.


What is the difference between go run and go build?

After the code ran successfully, I continued by executing:

go build

The terminal showed no success prompt.

When I first saw the silence, I even doubted:

Did it actually build?

After checking the project directory, I found an extra file:

hello-go.exe

The directory became:

01-hello-go/
├── go.mod
├── main.go
└── hello-go.exe

It turns out that when go build succeeds, it usually doesn't specifically output a "build successful" message.

No error message basically means the build is complete.

Then run the generated executable file:

./hello-go.exe

You can also see:

Hello, Go!
I am a frontend developer, learning Go full-stack development.

For now, these two commands can be understood like this.

go run .

Read the code in the current directory
→ Compile
→ Run immediately

Suitable for quickly viewing run results during development and learning.

go build

Read the code in the current directory
→ Compile
→ Generate an executable file

Suitable for getting a directly runnable program.

There is also an easily overlooked detail:

After modifying main.go, the original hello-go.exe will not automatically update.

If you want the executable file to contain the latest code, you need to re-execute:

go build

This is very different from the hot-reload experience in frontend development.

The .exe compiled by Go is the result of a specific build. Changes to the source code are not automatically synced to the previously generated executable file.


Officially Starting to Learn Variables

After completing the first project, I created a second practice directory:

D:\LearningGO\02-variables

Entered the directory and initialized the module:

cd /d/LearningGO
mkdir 02-variables
cd 02-variables
go mod init variables

There are several common ways to declare variables in Go.


Using var for Full Variable Declaration

The first way is to write out the variable name, type, and value simultaneously:

var name string = "They call me Baldy"
var age int = 30
var isFrontendDeveloper bool = true

Its structure is:

var variableName type = value

It's quite close to TypeScript's syntax:

let name: string = "They call me Baldy";
let age: number = 30;
let isFrontendDeveloper: boolean = true;

The difference is that TypeScript writes the type after the variable name, separated by a colon:

let age: number = 30;

Go writes the type directly after the variable name:

var age int = 30

Letting Go Infer the Type Automatically

When the variable type can be inferred from the value on the right, you can also omit the type:

var name = "They call me Baldy"
var age = 30

Go will infer the type based on the value on the right:

name → string
age  → int

Although the type is not explicitly written, the variable still has a definite type.

This does not mean it becomes a dynamically typed variable like in JavaScript.

For example:

var age = 30

Go infers age as int. You still cannot assign a string to it later:

age = "30"

Using := to Declare Variables

Inside a function, you can also use short variable declaration:

job := "Frontend Development"
learning := "Go Full-Stack Development"

This syntax is very short and is a very common way to declare variables in Go code.

For example:

name := "Starry"

Can be initially understood as:

Declare a new variable named name
→ Infer the type based on the value on the right
→ Assign "Starry" to it

But note:

:= can only be used inside functions.

The following code is allowed:

package main

func main() {
	name := "Starry"
}

But you cannot write it outside a function like this:

package main

projectName := "Frontend to Go Full-Stack Learning"

func main() {
}

Package-level variables need to use var:

package main

var projectName = "Frontend to Go Full-Stack Learning"

func main() {
}

:= and =: They look similar, but their roles are different

This was the place I was most likely to confuse when I first started learning variables.

First, declare two variables:

name := "They call me Baldy"
age := 30

Later, when modifying them, write:

name = "Starry"
age = 31

The complete code is as follows:

package main

import "fmt"

func main() {
	name := "They call me Baldy"
	age := 30

	fmt.Println("Name before modification:", name)
	fmt.Println("Age before modification:", age)

	name = "Starry"
	age = 31

	fmt.Println("Name after modification:", name)
	fmt.Println("Age after modification:", age)
}

Run result:

Name before modification: They call me Baldy
Age before modification: 30
Name after modification: Starry
Age after modification: 31

The difference here is:

:=  Declare a new variable and assign a value
=   Reassign a value to an existing variable

If you repeatedly use := for the same variable:

name := "Starry"
name := "Chen Hongdong"

Go will directly report an error:

no new variables on left side of :=

Translated, it means:

There are no new variables on the left side of :=.

The second line just wants to modify the existing name, the correct way to write it should be:

name := "Starry"
name = "Chen Hongdong"

As long as there is a new variable on the left side of :=, it can continue to be used

The following situation is allowed:

name := "Starry"
name, age := "Chen Hongdong", 30

Because in the second line:

name: already exists, reassign a value to it
age: appears for the first time, declare a new variable

As long as there is at least one new variable in the current scope on the left side of :=, the short variable declaration can be used.

When I first saw this rule, I did find it a bit convoluted.

But combined with the error message, it's easier to understand:

:= is not an ordinary assignment symbol; it also bears the responsibility of declaring variables.


Once a variable's type is determined, it cannot be changed arbitrarily

I declared an integer:

age := 30

Then deliberately tried to assign a string to it:

age = "31"

The Go compiler prompted:

cannot use "31" (untyped string constant) as int value in assignment

age was inferred as int type when declared.

So subsequently, only compatible integer values can be assigned to it; it cannot suddenly be changed to a string.

In JavaScript, you can write:

let age = 30;

age = "31";

But Go does not allow this.

This is also a very obvious difference between Go and JavaScript:

JavaScript: The same variable can hold different types of data during runtime
Go: Once a variable's type is determined, it cannot be changed arbitrarily

These kinds of problems are caught directly at the compilation stage.

You won't suddenly discover after the code runs that a value that was supposed to be a number has become a string.


Constants cannot be modified after declaration

Go uses const to declare constants:

const language = "Go"

If you try to reassign:

language = "Java"

The compiler will prompt:

cannot assign to language

Because once a constant is declared, it cannot be reassigned.

This has some similarity to const in JavaScript:

const language = "Go";

But Go's constants have their own rules and characteristics. For now, just remember the most basic point:

var: variable, can be reassigned
const: constant, cannot be reassigned

Using %T to Check Variable Types

To confirm what type Go actually inferred for the variables, I used fmt.Printf and %T:

package main

import "fmt"

func main() {
	name := "Starry"
	age := 30
	progress := 10.5
	isLearning := true

	fmt.Printf("name value: %s, type: %T\n", name, name)
	fmt.Printf("age value: %d, type: %T\n", age, age)
	fmt.Printf("progress value: %.1f, type: %T\n", progress, progress)
	fmt.Printf("isLearning value: %t, type: %T\n", isLearning, isLearning)
}

Run result:

name value: Starry, type: string
age value: 30, type: int
progress value: 10.5, type: float64
isLearning value: true, type: bool

The formatting symbols used so far include:

Format Symbol Purpose
%s Output string
%d Output decimal integer
%f Output floating-point number
%.1f Output floating-point number with one decimal place
%t Output boolean value
%T Output variable type
%v Output value in default format
%q Output string in quoted format
\n Newline

This output also showed me several of the most basic data types in Go:

string
int
float64
bool

Go variables are not undefined, but have zero values

In JavaScript, declaring a variable without assigning a value:

let name;

console.log(name); // undefined

Go is a bit different.

If you use var to declare a variable without assigning a value:

var name string
var age int
var progress float64
var isLearning bool

These variables will automatically get the zero value of their corresponding type.

The experiment code is as follows:

package main

import "fmt"

func main() {
	var name string
	var age int
	var progress float64
	var isLearning bool

	fmt.Printf("name value: %q, type: %T\n", name, name)
	fmt.Printf("age value: %d, type: %T\n", age, age)
	fmt.Printf("progress value: %.1f, type: %T\n", progress, progress)
	fmt.Printf("isLearning value: %t, type: %T\n", isLearning, isLearning)
}

Output:

name value: "", type: string
age value: 0, type: int
progress value: 0.0, type: float64
isLearning value: false, type: bool

The zero values for common basic types are as follows:

Type Zero Value
string ""
int 0
float64 0
bool false

This means:

var age int

is not a variable with no value at all.

From the moment it's declared, it already has a definite value:

0

This characteristic will be encountered repeatedly later.

For example, when converting a string to an integer fails, the returned integer result might be 0. But this 0 is just the zero value of int and cannot be used alone to judge whether the conversion was successful.


Go does not allow local variables and imported packages to be idle

I tried declaring a variable in a function but not using it:

package main

func main() {
	name := "Starry"
}

It directly reported an error after running:

declared and not used: name

Next, I imported fmt but didn't call anything from it:

package main

import "fmt"

func main() {
	name := "Starry"
	_ = name
}

The compiler continued to prompt:

"fmt" imported and not used

Here:

_ = name

can be temporarily understood as handing name to the blank identifier, thereby explicitly ignoring it.

In frontend projects, unused variables and unused imports are usually checked by ESLint or TypeScript.

Depending on the project configuration, they might be warnings or errors.

Go is more direct:

If local variables and imported packages are not used, the code cannot pass compilation.

When first writing practice code, this is indeed a bit annoying.

Often, I just temporarily wrote a variable, planning to use it later, and Go was already reporting an error.

But from a code maintenance perspective, this also prevents a large number of meaningless variables and dependencies from lingering in the project long-term.


Batch Variable Declaration

When there are many variables, they can be declared centrally:

var (
	name       = "Starry"
	age        = 30
	isFrontend = true
)

You can also declare multiple variables simultaneously in a function:

city, language := "Singapore", "Go"

In effect, it's similar to:

city := "Singapore"
language := "Go"

Besides this, you can also use var to declare multiple variables at once:

var name, job string

Because no values are assigned, they will all get the zero value of string:

""

Variable Scope

After learning variable declaration, I started testing the scope of variables.

Currently, I mainly encountered three situations:

Package-level scope
Function scope
Block scope

Package-level Scope

Variables written outside functions belong to package-level variables:

package main

var projectName = "Frontend to Go Full-Stack Learning"

func main() {
}

Functions in the same package can access it.

It should be noted that package-level variables cannot use :=; they must be declared using var or other methods.


Function Scope

Variables written inside a function can only be used within that function:

func main() {
	name := "Starry"
}

The name here can only be accessed within the main function.

If another function tries to access it directly, it will report an error.


Block Scope

Variables written inside an if code block are only valid within that block:

if isFrontend {
	target := "Become a full-stack developer"
	fmt.Println(target)
}

target only exists within this pair of curly braces.

If accessed outside:

fmt.Println(target)

The compiler will prompt:

undefined: target

This is quite similar to using let to declare block-level variables in JavaScript:

if (true) {
  let target = "Become a full-stack developer";
}

console.log(target); // Cannot access

An Easily Overlooked Issue: Variable Shadowing

I did another experiment:

package main

import "fmt"

func main() {
	language := "JavaScript"

	fmt.Println("Before entering if:", language)

	if true {
		language := "Go"
		fmt.Println("Inside if:", language)
	}

	fmt.Println("After leaving if:", language)
}

Output:

Before entering if: JavaScript
Inside if: Go
After leaving if: JavaScript

After seeing the result, it's clear:

language := "Go"

did not modify the outer language.

It declared a new variable with the same name inside the if code block.

The inner variable temporarily obscured the outer variable; this situation is called variable shadowing.

If you want to modify the outer variable, you should use:

if true {
	language = "Go"
}

The complete code is as follows:

package main

import "fmt"

func main() {
	language := "JavaScript"

	fmt.Println("Before entering if:", language)

	if true {
		language = "Go"
		fmt.Println("Inside if:", language)
	}

	fmt.Println("After leaving if:", language)
}

At this point, the output becomes:

Before entering if: JavaScript
Inside if: Go
After leaving if: Go

This experiment again illustrates:

:= may create a new variable
=  modifies an existing variable

In actual business code, if you don't pay attention to scope and misuse :=, you might encounter:

The code clearly assigned a value
→ But after leaving the code block
→ The outer variable still holds the original value

These kinds of problems might not necessarily report errors directly, making them even more worthy of vigilance.


They're All Numbers, Why Won't Go Let Me Add Them?

After learning variables, I created a third practice project:

03-data-types

Then declared two numbers:

age := 30
progress := 12.5

Used %T to check the types:

fmt.Printf("age: %v, type: %T\n", age, age)
fmt.Printf("progress: %v, type: %T\n", progress, progress)

Output:

age: 30, type: int
progress: 12.5, type: float64

Next, I tried to add them directly:

result := age + progress

The Go compiler immediately prompted:

invalid operation: age + progress (mismatched types int and float64)

As a frontend developer, my first reaction was:

Aren't they all numbers?

In JavaScript and TypeScript, integers and decimals usually both belong to number:

const age: number = 30;
const progress: number = 12.5;

const result = age + progress;

Go distinguishes them into different types:

30   → int
12.5 → float64

Even if they are both numbers, variables of different types cannot be directly operated on.

An explicit type conversion must be performed:

result := float64(age) + progress

The complete code is as follows:

package main

import "fmt"

func main() {
	age := 30
	progress := 12.5

	result := float64(age) + progress

	fmt.Printf("Calculation result: %v\n", result)
	fmt.Printf("Result type: %T\n", result)
}

Output:

Calculation result: 42.5
Result type: float64

This is also the Go style I gradually came to feel:

Go doesn't like to guess the developer's intent behind the scenes.

Whether to convert the integer to a float or the float to an integer needs to be explicitly chosen by the developer.


Converting float64 to int, the decimal part just disappears

Next, I tried converting float64 to int:

price := 19.99
integerPrice := int(price)

Checked the value and type before and after conversion:

fmt.Printf("Before conversion: %v, type: %T\n", price, price)
fmt.Printf("After conversion: %v, type: %T\n", integerPrice, integerPrice)

Output:

Before conversion: 19.99, type: float64
After conversion: 19, type: int

int(19.99) does not yield 20, but:

19

When converting a floating-point number to an integer, the decimal part is directly truncated, not automatically rounded:

int(19.99) // 19
int(19.50) // 19
int(19.01) // 19

Negative numbers are also truncated towards zero:

int(-19.99) // -19

If prices and precision are involved, this conversion requires extra caution.

The lost 0.99 won't report an error, nor will it remind you.

It just quietly disappears.

Moreover, monetary amounts in real business are usually not suitable for direct handling with floating-point numbers, because floating-point numbers themselves have precision issues. A more common approach is to use the smallest currency unit to store integers, for example:

19.99 yuan
→ Store as 1999 cents

Specific amount handling methods will be studied separately later.


I thought string(65) would give "65"

Next, I started testing conversions between numbers and strings.

Following JavaScript habits, I naturally wrote:

number := 65
result := string(number)

I originally thought it would yield:

"65"

The actual result was:

Value: "A"
Type: string

The complete experiment code is as follows:

package main

import "fmt"

func main() {
	number := 65
	result := string(number)

	fmt.Printf("Value: %q\n", result)
	fmt.Printf("Type: %T\n", result)
}

The reason is:

string(65)

does not format the integer into a decimal string, but converts 65 as a Unicode code point.

The character corresponding to the number 65 happens to be:

A

Therefore:

string(65) // "A"

Whereas in JavaScript:

String(65); // "65"

In Go, if you truly want to convert an integer into a decimal number string, you need to use strconv.Itoa.


Using strconv for Number and String Conversion

strconv is a package in Go's standard library used for conversions between basic data types and strings.

You need to import it before use:

import "strconv"

If you also need to output results, you can import fmt and strconv simultaneously:

import (
	"fmt"
	"strconv"
)

int to string

To convert int to its corresponding decimal string, you can use:

strconv.Itoa()

For example:

package main

import (
	"fmt"
	"strconv"
)

func main() {
	age := 30
	ageText := strconv.Itoa(age)

	fmt.Printf("Before conversion: %v, type: %T\n", age, age)
	fmt.Printf("After conversion: %v, type: %T\n", ageText, ageText)
}

Output:

Before conversion: 30, type: int
After conversion: 30, type: string

Although the terminal displays 30 in both cases, their actual types are different:

30   → int
"30" → string

So, the following two ways of writing express completely different things:

string(65)       // "A"
strconv.Itoa(65) // "65"

This is one of the easiest places to misremember at this stage.


string to int

To convert a string to an integer, you can use:

strconv.Atoi()

For example:

package main

import (
	"fmt"
	"strconv"
)

func main() {
	ageText := "30"

	age, err := strconv.Atoi(ageText)

	fmt.Printf("Conversion result: %v\n", age)
	fmt.Printf("Result type: %T\n", age)
	fmt.Printf("Error info: %v\n", err)
}

Output:

Conversion result: 30
Result type: int
Error info: <nil>

Two results are obtained at once here:

age: the converted integer
err: the error generated during conversion

This conversion was successful, so err is:

<nil>

At this stage, it can be initially understood as:

No error occurred

As for why a function can return two values at once, and what exactly error and nil are, these will be formally studied in the next article.


What happens when converting "30 years old" to int?

Next, I deliberately passed in a string that cannot be normally converted:

ageText := "30 years old"

age, err := strconv.Atoi(ageText)

The complete code is as follows:

package main

import (
	"fmt"
	"strconv"
)

func main() {
	ageText := "30 years old"

	age, err := strconv.Atoi(ageText)

	fmt.Printf("Conversion result: %v\n", age)
	fmt.Printf("Error info: %v\n", err)
}

Output:

Conversion result: 0
Error info: strconv.Atoi: parsing "30 years old": invalid syntax

The 0 here does not represent a successful conversion.

It is just the zero value of the int type.

So you cannot judge failure solely based on the conversion result:

if age == 0 {
	fmt.Println("Conversion failed")
}

Because if the user actually passes in "0", the conversion result is also 0.

For example:

age, err := strconv.Atoi("0")

At this point:

age = 0
err = nil

This is actually a successful conversion.

Therefore, you should judge whether the conversion failed based on err:

package main

import (
	"fmt"
	"strconv"
)

func main() {
	ageText := "30 years old"

	age, err := strconv.Atoi(ageText)

	if err != nil {
		fmt.Println("Incorrect age format:", err)
		return
	}

	fmt.Println("Age:", age)
}

Run result:

Incorrect age format: strconv.Atoi: parsing "30 years old": invalid syntax

This time, I just first learned how to use this code:

if err != nil {
	return
}

Regarding err != nil, return, and Go's error handling methods, these will be studied separately in the next article.


string to float64

To convert a string to a float, you can use:

strconv.ParseFloat()

For example:

package main

import (
	"fmt"
	"strconv"
)

func main() {
	priceText := "19.99"

	price, err := strconv.ParseFloat(priceText, 64)

	if err != nil {
		fmt.Println("Incorrect price format:", err)
		return
	}

	fmt.Printf("Price: %v\n", price)
	fmt.Printf("Type: %T\n", price)
}

Output:

Price: 19.99
Type: float64

Here:

strconv.ParseFloat(priceText, 64)

can be temporarily understood as:

priceText: the string to convert
64: wish to parse with float64 precision

If the string is not a valid floating-point number, for example:

priceText := "19.99 yuan"

err will not be nil.


float64 to string

Conversely, to convert float64 to a string, you can use:

strconv.FormatFloat()

For example:

package main

import (
	"fmt"
	"strconv"
)

func main() {
	price := 19.99

	priceText := strconv.FormatFloat(price, 'f', 2, 64)

	fmt.Printf("Before conversion: %v, type: %T\n", price, price)
	fmt.Printf("After conversion: %v, type: %T\n", priceText, priceText)
}

Output:

Before conversion: 19.99, type: float64
After conversion: 19.99, type: string

For now, its parameters can be understood like this:

strconv.FormatFloat(price, 'f', 2, 64)
Parameter Purpose
price The floating-point number to convert
'f' Use standard decimal format
2 Keep two decimal places
64 The original value is float64

For example:

price := 19.9
priceText := strconv.FormatFloat(price, 'f', 2, 64)

The resulting string is:

"19.90"

It should be noted here that keeping two decimal places describes the format when generating the string, not solving floating-point precision issues.


Type Conversions Used in This Phase

The type conversions actually used so far can be organized into the following table:

Conversion Direction Syntax
int to float64 float64(value)
float64 to int int(value)
int to string strconv.Itoa(value)
string to int strconv.Atoi(value)
string to float64 strconv.ParseFloat(value, 64)
float64 to string strconv.FormatFloat(value, 'f', 2, 64)

Among them, the easiest to misremember is:

string(65)       // "A"
strconv.Itoa(65) // "65"

When seeing string() in Go, you cannot directly understand it in the way of String() in JavaScript.


From a Frontend Perspective, What's Different About Go's Type Conversion?

After this round of practice, I found that JavaScript and Go have distinctly different approaches to type conversion.

JavaScript often performs implicit type conversions automatically:

"10" + 5; // "105"
"10" - 5; // 5

Both involve strings and numbers in operations, but the results can be completely different.

If you don't understand JavaScript's type conversion rules, it's easy to write code that produces seemingly strange results.

Go's approach is more direct.

The following code cannot pass compilation:

text := "10"
number := 5

result := text + number

The developer must first explicitly decide what to do.

If you want to perform string concatenation, you can convert the integer to a string:

result := text + strconv.Itoa(number)

The result is:

105

If you want to perform a mathematical operation, you must first convert the string to an integer:

textNumber, err := strconv.Atoi(text)

if err != nil {
	fmt.Println("Conversion failed:", err)
	return
}

result := textNumber + number

The result is:

15

Go does not guess for the developer:

Do you want to concatenate strings
or perform a mathematical operation?

It must be explicitly expressed by the code.


Conclusion

This article actually covered more content than I originally expected.

Starting from the first program, I learned all the way through:

Creating a Go Module
→ Writing main.go
→ go run
→ go build
→ Variables and Constants
→ := and =
→ Type Inference
→ Zero Values
→ Scope
→ Variable Shadowing
→ int and float64
→ Number and String Conversion

My most distinct feeling about Go at this stage is:

Go doesn't like to guess on behalf of the developer.

Once a variable's type is determined, it cannot be changed arbitrarily.

Variables of different numeric types cannot be directly calculated.

When converting a floating-point number to an integer, the decimal part is directly truncated.

When converting an integer to a string, you can't just see string() and directly apply the understanding of String() from JavaScript.

At first, it feels a bit "strict":

Aren't they all numbers?
Why do I need to convert manually?
I just want to change a variable, why can't I continue using :=?
I clearly wrote a variable, why can't I temporarily not use it?

But these error messages are also constantly reminding me:

What type is this variable exactly?
Is this declaring a variable or modifying a variable?
Will data be lost after type conversion?
Can the string passed by the user really be converted?
Did this conversion actually succeed, or did I just get a zero value?

These questions might not arise frequently when writing pages, but they are hard to avoid when processing backend data.

The parameters passed by users in requests are often strings to begin with:

?page=1
&size=20
&price=19.99

If the backend wants to use this data for pagination, calculations, and database queries, it must first complete the conversions and handle possible errors.

Currently, I have written this many times in strconv.Atoi and strconv.ParseFloat:

value, err := ...

But I haven't formally figured out:

Why can a function return multiple values?
What type is err exactly?
What does nil represent?
Why is if err != nil everywhere in Go code?
Besides ending a function, what else can return do?

So in the next article, I will formally enter:

Functions
→ Parameters and Return Values
→ Multiple Return Values
→ error
→ nil
→ Error Handling

See you in the next article.