---
title: 'Environment Setup and the First Program'
source: 'https://academia.sh/en/courses/go-fundamentals/setup-and-first-program'
course: 'Go Fundamentals'
language: en
updated: '2026-09-13T05:08:45+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Removes each of the three mandatory parts a source needs to run and classifies the compile result one by one'
    - 'Runs proof that two files in the same package see each other without writing an import'
    - 'Shows that the module file stands outside the source by removing it while the source stays unchanged'
---

# Environment Setup and the First Program

Three things are mandatory for a Go source to compile: a package declaration, an entry point (func main), and a module file; when one of the three is missing, compilation is rejected even if the rest of the source has not changed at all — the module file never sits inside the source itself.

The previous lesson measured four rejection reasons; all of them arose when something
**extra** was written into the source or a promise went unkept. This lesson asks the reverse:
what is the smallest set of things that must be written for a source to compile and run at
all? A Go program asks for three things at once — a package declaration, an entry point, a
module file — and when one of them is missing, compilation stops no matter how correctly the
rest of the source is written.

The How Computers Work course (M01/K01) built, as a concept, how a program is started by the
operating system, the notion of an entry point, and how a process reaches its first
instruction. **That concept is not rebuilt here** — one sentence is enough to state the
difference: there, it was explained that an entry point **has to exist**; here we measure
**which signature** and **at what point in compilation** Go enforces that requirement.

## Three Mandatory Parts

The three each sit in a different place. The package declaration (`package main`) is the
**first line** of the source and states which package it belongs to; for an executable
program, this package name has to be `main`. The entry point is the `func main()` function
defined inside the `main` package, and it is the first Go instruction called directly by the
operating system, not by a virtual machine. The module file, on the other hand, sits
**outside** the source — a separate text file living in the same directory as the source,
`go.mod`, and it is the only thing that tells the compiler "this directory is the root of a
module".

All three being mandatory is not a coincidence — it is the answer to three separate questions.
The package declaration answers "what is this source part of" and states that not even a
single source file can stand without a package — in Go there is no such thing as a
packageless source, even the smallest program has to carry a package name. The entry point
answers "does execution start here" — a package can hold more than one function, but there is
exactly one signature that tells the operating system which function to start from. The module
file answers "where does this package sit" and carries information independent of the package
itself — the same package name can repeat across many different directories, but which module
a directory belongs to is read only from the `go.mod` file in that directory (or one above it).

Since all three questions look "mandatory," it can be tempting to check them together as a
single checklist — one check asking "are all three complete." This lesson does the opposite:
it removes each one **one at a time** and reads the result separately, because the same
`not compiled` answer can hide three separate reasons, and knowing at which stage each of the
three arises is the first step to understanding why a source does not compile.

We are going to remove these three parts one at a time and measure the compile result. The
measurement tool is very close to the previous lesson's `compiles` function; the only
difference is that it also takes, as a separate parameter, whether the module file will be
written.

```go
// build.go — helper that compiles while checking each part
package main

import (
	"os"
	"os/exec"
	"path/filepath"
)

func compilesWithParts(source string, writeMod bool) bool {
	dir, err := os.MkdirTemp("", "trial")
	if err != nil {
		return false
	}
	defer os.RemoveAll(dir)
	if writeMod {
		os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module trial\n\ngo 1.24\n"), 0o644)
	}
	os.WriteFile(filepath.Join(dir, "code.go"), []byte(source), 0o644)
	cmd := exec.Command("go", "build", "./...")
	cmd.Dir = dir
	cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local")
	return cmd.Run() == nil
}

func classify(ok bool) string {
	if ok {
		return "compiled"
	}
	return "not compiled"
}
```

**LB4.** The measurement tool opens one parameter further than the previous lesson:
`writeMod` decides whether the module file is written for that run. The source itself is
never affected by this parameter; keeping that in mind is necessary to correctly read what the
fourth line below measures.

This extension looks small but carries a design decision: in the previous lesson, where the
unwritten thing sat was always **inside** the source — a line either existed or it did not. In
this lesson, for the first time, where the unwritten thing sits moves **outside** the source,
and the measurement tool has to represent that with a separate argument. The next section shows
why this distinction matters.

We run four cases in order: with nothing missing, with no package declaration, with no entry
point, with no module file.

```go
// parts.go — each of the three mandatory parts is removed one at a time
package main

import "fmt"

const completeSource = `package main

func main() {}
`
const noPackageSource = `func main() {}
`
const noMainSource = `package main
`

func main() {
	fmt.Printf("%-30s %s\n", "missing part", "result")
	fmt.Printf("%-30s %s\n", "nothing missing", classify(compilesWithParts(completeSource, true)))
	fmt.Printf("%-30s %s\n", "no package declaration", classify(compilesWithParts(noPackageSource, true)))
	fmt.Printf("%-30s %s\n", "no entry point (main)", classify(compilesWithParts(noMainSource, true)))
	fmt.Printf("%-30s %s\n", "no module file", classify(compilesWithParts(completeSource, false)))
}
```

```
missing part                   result
nothing missing                compiled
no package declaration         not compiled
no entry point (main)          not compiled
no module file                 not compiled
```

The first line is the control group: with nothing missing, the source compiles. Each of the
next three lines removes exactly one part from the control group, and the result changes in the
same direction each time — all three stop compilation. But the three do not stop it for the
same **reason**. With no package declaration, the compiler has no idea at all which package the
source belongs to; this is the earliest branch to be cut, because a package name is expected on
the source's very first line. With no entry point, the source **compiles** as a valid package,
but no executable is produced — there is no error in the source, it is only missing an
executable end; `go build ./...` counts this too as not compiled, because an entry point is a
mandatory contract for the `main` package. The fourth line is the most indirect and leads into
this lesson's limiting measurement.

Looking at the `noPackageSource` constant, exactly one line is missing: `package main`. When
that line is removed, what remains — `func main() {}` — is not meaningless on its own: it is a
function definition and its syntax is correct. The compiler's rejection here is not about
syntax but about **context**: every Go file is read as part of a package, and without a
package name it cannot place a file into any package at all. This is a different tone from the
four rejections in the previous lesson: those four sources violated syntax rules, whereas this
source is a syntactically complete function body that is **nameless**.

The first two rejections belong to a family close to the four rejections the previous lesson
measured: both are something left missing in the source's own syntax, and the compiler sees
this while **reading** the source. The absence of the entry point is caught at a different
stage. `go build` does two things one after the other: it first compiles the source into a
package, then it **links** an executable from that package. Package compilation accepts
`main.go` as a valid Go file; the linking stage looks for the `main` package to carry a
`func main()` function and stops when it cannot find one. This distinction matters because the
same source could compile without any problem in a different context — for instance as a
library package imported by another program; the entry-point requirement applies only to the
`main` package, only when an executable is being requested.

## Two Files, One Package

Do the three mandatory parts have to sit in a single file? No. Two files in the same directory,
as long as they carry the same package declaration, count as belonging to **one package** and
are compiled together — even if one of them does not carry a `func main`. The `build.go` file
above and every other file used in this lesson were already showing this; here we build the
same pattern in its plainest form.

The practical importance of this shows up as a program grows. If everything had to be written
into a single file carrying `func main()`, a program's length would be limited by what a
single file can stay readable at. Because Go defines a package by its declaration and not by
file count, a package's responsibilities can be spread across more than one file — and this
split never changes the compile result: `build.go`, `helper.go`, and `together.go` would
compile just as well as three separate files or merged into one, and would produce the same
binary. The decision to split exists purely for the convenience of the reader and the writer.

```go
// helper.go — second file, belongs to the same package
package main

import "fmt"

func greet(name string) string {
	return fmt.Sprintf("hello, %s", name)
}
```

```go
// together.go — two separate files, one package, compiled together
package main

import "fmt"

func main() {
	fmt.Println(greet("package"))
}
```

```
hello, package
```

**LB5.** `together.go` calls the `greet` function defined in `helper.go` without writing any
import at all. What is measured in this lesson is not the mere existence of a second file — we
were already using one before reaching this lesson — it is that two separate files compile into
**a single binary**: `go run .` reads every `.go` file in the directory, never looks for an
import between them, and checks only that the package name matches.

This behavior asks us to re-read the "nothing missing" line from the earlier measurement too.
The single-file `completeSource` used in that line was written and compiled on its own,
**outside** `build.go` — `classify` and `compilesWithParts` were not being called there, the
source only carried `package main` and an empty `func main`. Here, on the other hand,
`together.go` depends on `helper.go` in the same directory, and that dependency needs no import
crossing a file boundary at all. The two measurements complement each other: one shows the
smallest possible shape of a package, the other shows the shape of a package spread across more
than one file.

This also explains why a package declaration is written per file. The compiler learns which
files together form a whole not from the **file name** but from each file's own first line.
`build.go`, `helper.go`, and `together.go` all write `main`, and all three join the same
package; if any one of them had written a different name, compilation would stop right there,
even inside the same directory.

One consequence of this behavior is that imports work at **two separate levels**.
`together.go` imports the `fmt` package on its own, because it uses a piece of the standard
library; but it does not import `greet`, defined in `helper.go`, because that already belongs
to the same package. The import line is only needed for names that cross a **package
boundary** — inside the same package, a file boundary carries no meaning. The course's Packages
topic will flip this boundary the other way: there, files will still stay in the same package,
but some names will not be visible **outside** the package at all.

## Limiting Measurement: The Module File Is Not Part of the Source

Return to the fourth line. The source used in the `no module file` line is **byte for
byte identical** to the one used in the first line — the `completeSource` constant passes
through both calls unchanged. The only difference is that the `writeMod` argument given to
`compilesWithParts` is `false`: no `go.mod` is ever written into the temporary directory.

**LB6.** What is measured in this line is not a source error but a **missing context**. The
source's own package declaration and entry point stand complete; the compiler still rejects it,
because it will not agree to compile a directory without knowing which module it belongs to.

This line separates qualitatively from the lesson's first two rejections. Removing the package
declaration or the entry point meant **touching** the source; here the source was never even
opened, only another file in the directory was deleted. A reader looking at the
`completeSource` constant could not tell from the source itself why it sometimes compiles and
sometimes does not — the answer sits outside the source, in the directory's contents.

The practical consequence is this: copying a source file into another directory is useless if
that directory has no module file — the source will not compile even if it was copied exactly
as is. The module file is not **part of** the source the way the package declaration is; it is
the source's **context**. This distinction matters because, even though both are counted
"mandatory," they are mandatory for different things: the package declaration carries the
source's own identity, the module file states **where** the source sits. The course's Packages
topic will deepen this distinction; what is measured here is only the requirement itself — a
source, however correctly written, never turns into a binary without a module file.

The measurement tool used in this lesson never changed the module file's **content** — it only
changed whether it existed. A module file, even in its smallest form asking for no outside
dependency at all, is mandatory on its own; having no dependency line added to it is not a
reason for the compiler to reject the module. What is mandatory is the module file's
**existing**, not how full its content is.

The module file being independent of the source also means the same source can be moved
**under more than one** module. Changing directory and writing a fresh `go.mod`, without
changing the source at all, makes it part of another module. Changing the package declaration,
on the other hand, means changing the source itself. This difference between the two shows that
the word "mandatory" does not carry a single meaning — something can be mandatory **inside**
the source, or mandatory **around** the source, and the cost of removing each is not the same:
one breaks the source, the other only invalidates the place where the source sits.

This threefold distinction — identity, contract, context — also gives a retrospective frame to
the four rejections measured in the first lesson. The unused variable and the unused import
were about the source's own identity: every name written in the source should deserve to stay
part of the source. The unwritten return value was a broken contract: the signature makes a
promise, the body must keep it. The third category added in this lesson — context — never
appeared in the first lesson, because the four sources there were already being written into a
module. Seeing that mandatoriness can sit in three separate places will keep proving useful
later in the course: a variable's zero value will belong to its **identity**, a function's
named return will belong to its **contract**, and a package's export rule will belong to its
**context**. None of these three categories will be called by these names anywhere in this
course — what is built here is only a reading habit: the first question on seeing a
`not compiled` answer should not be "which line is missing" but "where does the missing thing
sit".

## Summary

- Three things are mandatory for a Go source to compile: a package declaration, an entry point
  (`func main`), a module file.
- Compilation stops when any of the three is missing, but not for the same reason: one leaves
  the source's identity incomplete, one its contract, one its context.
- Two files in the same directory see each other and compile into a single binary without
  writing an import, as long as they carry the same package name.
- The module file is the source's context, not its part: the same source, unchanged, stops
  compiling once the module file is removed.
- Mandatoriness does not carry one meaning — what is mandatory inside the source and what is
  mandatory around the source cost different things when removed.

## Next Step

This lesson measured what is required for a source to **run**; nowhere did it ask what is or
is not written into a value. `func main() {}` was an empty body and had no variable inside it
at all. The next lesson fills that gap: do you have to write a variable's type when defining
it, where does short declaration infer the type from, and who fills the unwritten values in a
sequential constant list?
