Lesson 3/41 · Go Language Intro & Dev Environment Beginner m01-l03

Your First Go Program and the go Command: Modules and Project Basics

Module 1: Go Language Intro & Dev Environment | Lesson: m01-l03 | Level: Beginner After this lesson you will be able to: understand the basic structure of a Go program, master the core usage of the go command, and develop a sound sense of module-based project layout.

Learning Objectives

  • Explain the roles of package main and func main as the entry point of a Go program.
  • Describe the import mechanism and how the capitalization of an identifier's first letter controls package visibility.
  • Use go mod init to initialize a module, and master common subcommands such as go run, go build and go vet.
  • Recognize and avoid common project-layout mistakes, such as relative imports or misusing the import restrictions of package main.

Core Concepts

2.1 The Basic Structure of a Go Program

2.1.1 Packages and the Entry Function

What: Go organizes code around "packages". A package is a group of source files in the same directory that share a single package declaration; it is the unit of compilation and code reuse. Every Go file must belong to exactly one package. Why: Go has no class inheritance. Packages are organized into a hierarchy by import path, but packages are not nested declarations, so the semantics are simpler than namespaces. How: - package main: declares that the current file belongs to the main package. Only a program built from the main package produces an executable binary. - func main(): the main package must contain exactly one main function with no parameters and no return values; it is the entry point of program execution.

2.1.2 import and Package Visibility

What: use the import keyword to pull in other packages. Why: Go isolates namespaces by package name, avoiding collisions. How: - Visibility rule: in Go, the visibility of an identifier (variable, function, type, etc.) is determined by the case of its first letter. A capital first letter means exported — other packages can access it; a lowercase first letter means unexported — it is visible only inside the package. - Import restriction: in module mode, import can only use a standard-library path or a module path; it does not support relative paths like ./sub. Relative imports are a leftover from the GOPATH era and are no longer available. - Important constraint: package main cannot be imported by other packages. If you try to import a package main, the compiler reports is a program, not an importable package. Therefore, when demonstrating "a capital first letter means accessible", you must use a non-main package.

2.2 The go Command in Depth

The Go toolchain provides a rich set of subcommands that are the core of day-to-day development.

2.2.1 go mod init

What: initialize the current directory as a Go module. Why: Go 1.16+ enforces module management. Without an initialized module, commands such as go get fail directly with go.mod file not found.... A single-file go run x.go still works, but dependency management and version recording must rely on go.mod. How: go mod init <module-name>. The module name usually follows the domain/project convention.

2.2.2 go run / build / install

  • go run: compiles and immediately runs a source file. Great for quickly testing a snippet.
  • go build: compiles source files. It produces an executable only when the package is main; for a library package it by default just checks that it compiles, without producing an output file.
  • go install: compiles and installs a package. The executable (a main package) is installed into GOBIN (default GOPATH/bin) and can be called directly; an ordinary library package is only compiled and checked, with no installed artifact. In module mode, to install a tool from another module use go install example.com/tool@latest.

2.2.3 go vet / fmt / doc / test / mod tidy

  • go vet: flags suspicious constructs, such as a mismatched fmt.Printf argument type. Such problems print garbage like %!d(...) rather than panicking, but they obscure the real result.
  • go fmt: formats code and unifies style.
  • go doc: shows documentation for the standard library or local packages.
  • go test: runs test files (named *_test.go).
  • go mod tidy: cleans up go.mod and go.sum, adding missing dependencies and removing unused ones.

Example Code

Example 1: Hello World and Module Initialization

This example creates a standard Go program structure and shows the basic use of package main and func main.

package main

import (
    "fmt"
)

func main() {
    // Print Hello World
    fmt.Println("Hello, World!")

    // Demonstrate a simple variable declaration and output
    name := "Go"
    fmt.Printf("Welcome to %s!\n", name)
}

Expected output:

Hello, World!
Welcome to Go!

Example 2: Visibility Demo and a vet Check

This example demonstrates two files: a non-main package mypkg showing the export rules, and a main package that calls it. It also shows how go vet flags a semantic error.

Note: to explain visibility within a single-file demo, we show only the main package code here. In a real project, mypkg should live in its own directory. To satisfy the "single file must be runnable" constraint, we write the mypkg logic directly inside the main package, but explain the visibility rules through comments.

package main

import (
    "fmt"
)

// ExportedFunc starts with a capital letter, meaning it is exported (accessible from other packages).
// Note: because this file belongs to package main, and package main cannot be imported,
// this function only takes effect within the current package. If moved into the mypkg package,
// other packages could then call it.
func ExportedFunc() string {
    return "I am exported"
}

// unexportedFunc starts with a lowercase letter, meaning it is unexported (visible only within this package).
func unexportedFunc() string {
    return "I am unexported"
}

func main() {
    // Call the exported function
    fmt.Println(ExportedFunc())

    // Call the unexported function
    fmt.Println(unexportedFunc())

    // Demonstrate a go vet check: the format verb does not match the argument type.
    // It compiles and runs, but go vet warns about it.
    // We deliberately write it wrong to show what vet does.
    var s string = "hello"
    // The line below triggers a go vet warning: format %d has arg s of wrong type string.
    // At runtime it prints %!d(string=hello).
    fmt.Printf("String value: %d\n", s)
}

Expected output:

I am exported
I am unexported
%!d(string=hello)

Common Mistakes & Pitfalls

  1. Misusing relative imports

    • Wrong code: import "./mypkg"
    • Why: relative-path imports are forbidden in Go 1.11+ module mode.
    • Correct approach: use the module path, e.g. import "example.com/myapp/mypkg".
  2. Misunderstanding the visibility of package main

    • Wrong assumption: "if a function starts with a capital letter, other packages can import and call it."
    • Why: package main is not an importable package. Even if a function is exported, other packages cannot import the main package.
    • Correct approach: put reusable code in a non-main package (such as mypkg) and import and call it from the main package.
  3. Believing go vet blocks compilation

    • Wrong assumption: "if go vet reports an error, the program cannot run."
    • Why: go vet is a static analysis tool and does not block compilation. It checks suspicious semantic points the compiler does not report (such as a Printf argument type mismatch).
    • Correct approach: make go vet part of your CI/CD pipeline to catch potential problems early.

Best Practices

  1. Always use module mode: the first thing you do in a new project is run go mod init, so that dependency versions stay under control.
  2. Follow package naming conventions: package names should be short, lowercase, and free of underscores, and should not clash with the standard library.
  3. Run go mod tidy regularly: run it before committing code so that go.mod and go.sum stay clean and free of redundant dependencies.
  4. Make good use of go vet and go fmt: run them continuously while coding, to keep style consistent and free of semantic errors.
  5. Distinguish executable packages from library packages: the main package only holds entry logic; core business logic should live in library packages, making it easier to test and reuse.

Exercises

  1. Concept question: explain why an exported function in package main cannot be imported by other packages.
  2. Coding exercise: create a module named calc that contains an Add function (capital first letter) and a sub function (lowercase first letter). Call Add from the main package and print the result.
  3. Advanced/debugging exercise: what is wrong with the following code? How do you fix it? ```go package main

    import ( "fmt" )

    func main() { var x int = 10 var y string = "hello" fmt.Printf("x: %d, y: %d\n", x, y) } ```

Reference Answers

Reference Answers (click to expand / collapse)

Question 1

Answer: because package main is the entry package of an executable program as defined by the Go language; the Go spec states that the main package cannot be imported by other packages. Even if its functions start with a capital letter (exported), other packages cannot access it through an import statement. This prevents the program entry point from being referenced by accident and keeps the program structure clear.

Question 2

Reference code:

package main

import (
    "fmt"
    "example.com/calc"
)

func main() {
    result := calc.Add(1, 2)
    fmt.Println("Result:", result)
}

Note: in a real project, the calc package should live in its own directory, containing an add.go file that defines func Add(a, b int) int { return a + b }. This is a single-file demo, assuming the module path is example.com/calc.

Question 3

The problem: fmt.Printf uses the %d verb to format the string variable y, causing a type mismatch. It compiles and runs, but the output is %!d(string=hello), and go vet warns about it. The fix: change the format verb for y to %s.

package main

import (
    "fmt"
)

func main() {
    var x int = 10
    var y string = "hello"
    // Fixed: changed %d to %s
    fmt.Printf("x: %d, y: %s\n", x, y)
}

Lesson Summary

  • A Go program enters through package main and func main.
  • The capitalization of an identifier's first letter controls visibility, but package main itself cannot be imported.
  • go mod init is the starting point of module management, and go run/build/vet are the core commands of daily development.
  • Avoid relative imports, and use go vet to catch semantic errors.

Next lesson preview: variables, constants and primitive types, including the zero-value mechanism and type conversion.

Feedback

This site is front-end only — no backend, no accounts — so feedback goes to GitHub Issues, Discussions, or email.

Paste it into the issue or email to help track it down — it never includes anything you typed

Also:Browse existing issues · Email feedback (no GitHub account needed):278975598@qq.com