JavaRush /Courses /Go SELF /Imports — grouping, aliases, and _‑ import

Imports — grouping, aliases, and _‑ import

Go SELF
Level 10, Lesson 3
Available

1. Why import exists and why Go forbids extras

When you write a program, you almost always use someone else’s code: printing, number parsing, working with strings, random numbers, time, and so on. In Go, you do that via import: you explicitly tell the compiler which packages are needed by this specific file. This is important: imports are not “for the whole project”, but per file, and Go treats this strictly.

That strictness shows up in the fact that unused imports are forbidden. Not “discouraged”, not “well, okay”, but a straight-up “no”. This is one of the ways Go forces us to keep dependencies clean. If a package isn’t used, then either you don’t need it, or you forgot to write the code that uses it. And it’s better to learn that immediately than a month later.

A mini example: the classic “hello world” with an import of fmt.

package main

import "fmt"

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

Now a typical beginner mistake: “I’ll import it for later”.

package main

import (
	"fmt"
	"math" // <- not used
)

func main() {
	fmt.Println("Hi")
}

This code won’t build: the compiler will say something like "imported and not used: math". That’s normal. That’s Go taking care of your future self who will read the code and understand what the program actually needs.

2. Import forms and ordering

Single and grouped import

Go imports come in two main forms: single and grouped. Both are perfectly valid — it’s not about “right/wrong”, it’s about readability. When there’s only one package, the single form is as simple as it gets. When there are two or more, grouping is almost always nicer.

Single import:

package main

import "fmt"

func main() {
	fmt.Println("one import") // one import
}

Grouped import:

package main

import (
	"fmt"
	"strconv"
)

func main() {
	fmt.Println(strconv.Itoa(2026)) // 2026
}

In practice, grouped import is the “default style” as soon as you have more than one dependency. It’s easier to extend: you add lines inside the parentheses and don’t think about formatting.

Intentional dependencies and readability

When you start writing larger programs, imports become your mini map: they show what the file is about. If you see fmt, strconv, strings, it looks like text processing and output. If you see net/http, there’s probably a server nearby. If you see time, then it’s about time or timeouts.

That’s why it’s useful to treat imports as the file’s “storefront”. If the storefront is cluttered, then inside there’s probably a mess too.

There are a few very practical principles that are easy to follow even as a beginner, without religious style wars.

  • It’s better to keep imports in alphabetical order, at least within the standard library.
  • Don’t import a package “just in case” — Go won’t allow it anyway.
  • If you removed code that used a package and the compiler started complaining, that’s a hint: delete the dependency.

A small logical diagram:

Added an import line -> took a dependency
Took a dependency -> made the file more complex
Made the file more complex -> reading became harder
Therefore -> the import should be intentional

At this point, Go feels like a strict teacher: “Please cross out the extra stuff.”

3. Mini tasker: practicing tidy imports

To keep the examples grounded, let’s continue with the same learning app. Let it be a small console utility, a “mini tasker”: it reads a task title and prints a normalized title and a “pseudo-ID”.

At our current level (without slices and structs) it will be a simple program, but it’s already useful as a trainer for tidy code and imports.

Version 1: read a string and print it as-is

Here we only need fmt.

package main

import "fmt"

func main() {
	var title string
	fmt.Scan(&title)

	fmt.Println("task:", title) // for example: task: BuyMilk
}

But you’ll quickly discover that input via Scan “cuts” the string at spaces. The user wanted "Buy milk", but got "Buy". For now, we’ll accept that as a given (we’ll talk about more advanced input later), and for now we’ll add simple normalization: trim spaces at the edges and make a “nice label”.

Version 2: add strings and grouped import

package main

import (
	"fmt"
	"strings"
)

func main() {
	var title string
	fmt.Scan(&title)

	title = strings.TrimSpace(title)
	fmt.Println("task:", title) // task: BuyMilk
}

We have two packages — and grouped import immediately looks natural.

Version 3: put it all together

Now let’s assemble the “mini tasker” into a slightly nicer form: normalize the input, make an “ID” as a number (even if it’s a simple one), and print the result.

Here we’ll use strings and strconv. Two packages — so we group them.

package main

import (
	"fmt"
	"strconv"
	"strings"
)

func main() {
	var raw string
	fmt.Scan(&raw)

	title := strings.TrimSpace(raw)
	id := strconv.Itoa(len(title))

	fmt.Println("id:", id, "task:", title) // id: 7 task: BuyMilk
}

Yes, an “ID by string length” isn’t a real identifier system. But for our current level it’s a great exercise: imports are clean, everything is used, the logic is small, and the program doesn’t fall apart.

4. Import aliases: when they’re actually needed

Sometimes the package name is inconvenient in a specific file. Or you imported two packages that have the same “last word”, and you want to distinguish them explicitly in code. Or you want to avoid a name conflict. For that, Go provides an import alias: you can rename a package within a single file.

The syntax looks like this:

import f "fmt"

And then instead of fmt.Println you write f.Println. This works, but it’s important to understand: an alias is not decoration. If you start shortening everything, the code will start to look like cryptography (and we’re not studying cryptography yet — at most, moral support).

Alias for readability: be careful

package main

import (
	"fmt"
	s "strings"
)

func main() {
	var title string
	fmt.Scan(&title)

	title = s.TrimSpace(title)
	fmt.Println("task:", title) // task: BuyMilk
}

You can do this, but the question is: did it get clearer? For beginners, usually the opposite: strings.TrimSpace is easier to read than s.TrimSpace. Use aliases when there’s a real reason.

Name conflict: crypto/rand vs math/rand

A very real situation: two packages want to be called the same thing in code. The classic example is crypto/rand and math/rand. Both are “rand”, but the meaning is different: one is for cryptography, the other is for ordinary pseudorandomness. Even if you’re not using both yet, it’s important to be able to read code like this.

package main

import (
	crand "crypto/rand"
	"fmt"
	mrand "math/rand"
)

func main() {
	_ = crand.Reader // just to show: the package exists
	fmt.Println(mrand.Intn(10)) // 0..9 (example)
}

Here aliases make the code honest: crand and mrand are different things, and you can see it immediately.

Alias vs shadowing

There’s a funny (and very annoying) mistake: you imported a package, and then accidentally named a variable the same as the package. This is called shadowing: a name from an inner scope overrides the outer one.

For example:

package main

import "fmt"

func main() {
	fmt.Println("before") // before

	fmt := "oops"
	_ = fmt

	// fmt.Println("after") // won't compile
}

Inside main, the name fmt became a string variable, and you can no longer access the fmt package.

How do you live with that? First, don’t name variables after packages. Second, if a conflict is still unavoidable (rare, but it happens), an import alias can save you.

package main

import f "fmt"

func main() {
	fmt := "still ok"
	_ = fmt

	f.Println("package still accessible") // package still accessible
}

This is an example where the alias is not “for beauty”, but a real “lifebuoy”.

5. _‑import: pulling in a package for initialization

There’s a special import form that looks strange, but appears in real projects:

import _ "some/package"

This import is called a blank import. The point is that you import a package but don’t introduce its name into your code. That is, you can’t write pkg.Something, because you don’t have pkg. So why would you need it?

In plain English: “I don’t need this package’s functions/types directly, but I need it to be linked in and to run its initialization.”

In Go, a package may have “initialization code” that runs automatically when the program starts. Usually this is about registering some capabilities. The most popular real-life example is database drivers: you don’t call them directly, but they “register” themselves inside database/sql. Another common example is registering decoding formats (for example, you import a format package, and a shared decoder learns how to understand it).

At our level, it’s important to lock in two things.

First: _‑import is not considered unused. That is, the compiler doesn’t complain because you explicitly said: “I need the fact of importing.”

Second: _‑import almost always implies a side effect, and side effects reduce predictability. So as a beginner, it’s best to treat _‑import like a sharp knife: it’s useful, but you shouldn’t swing it around in a crowd.

An “empty” example purely about syntax

This example doesn’t do anything useful, but it shows the mechanics: with a normal import of math the compiler will complain, but with _ it won’t.

package main

import (
	"fmt"
	_ "math"
)

func main() {
	fmt.Println("started") // started
}

Yes, it’s pointless. But it’s like a training dummy: you practice on it so you don’t practice on production later.

6. Typical mistakes when working with imports

Mistake #1: “I’ll import it in advance so I don’t forget later”.
In Go, that doesn’t work: the compiler won’t allow unused packages. And that’s actually good: you’re not carrying a brick in your backpack “just in case”. If a package isn’t needed — it’s not there. If it is needed — you add it and use it immediately, otherwise the build stops and honestly shows the problem.

Mistake #2: aliases for “style”, not for meaning.
Beginners sometimes turn code into a riddle: f.Println, s.TrimSpace, st.Atoi, e.New… Formally the program works, but it’s hard to read. An alias is good when it solves a specific problem: a name conflict, a strong clarity improvement, or rare shadowing of a package name. In all other cases, it’s better to keep the standard package name.

Mistake #3: confusing _ in an import with _ in code.
The _ symbol shows up in many places, but the meaning differs. In an import, _ "pkg" means “import for the side effect of initialization”. In an assignment, x, _ := ... means “there is a value, but I don’t need it”. If you mix these ideas up, you might start thinking _ is just “throw away something unnecessary”. With a blank import, you’re not “throwing away” — you’re “linking in silently”.

Mistake #4: expecting an import to “apply to the whole package”, not to a file.
Even if you have multiple files in the same package, imports are written in each file separately. This sometimes surprises people: “But I already imported fmt in another file!” — yes, but that other file doesn’t share imports with the current one. That’s how it’s designed: each file declares its own dependencies so it can be read and understood in isolation.

1
Task
Go SELF, level 10, lesson 3
Locked
Clean import
Clean import
1
Task
Go SELF, level 10, lesson 3
Locked
Task label
Task label
1
Task
Go SELF, level 10, lesson 3
Locked
Name conflict
Name conflict
1
Task
Go SELF, level 10, lesson 3
Locked
Start and passport
Start and passport
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION