Call Code in Another Module
Purpose and Scope
This page explains the tutorial step where an executable module imports a package from another module and calls one of its exported functions. In the public tutorial, the caller module is named example.com/hello, the library module is example.com/greetings, and the program imports example.com/greetings before calling greetings.Hello. The reader problem is not just how to type the import line; it is how Go connects module metadata, package names, selector expressions, and function-call checking into one workflow that can be built and run.
The repository evidence for this page sits mostly below the go command. Once module-aware loading has selected the package named by an import path, compiler and type-checker code validate that a selector such as greetings.Hello denotes a callable object, that the argument list matches the function signature, and that any generic instantiation rules are satisfied. The same call and selector concepts are implemented in the public go/types package and in the compiler’s internal types2 checker, while escape analysis later reasons about argument and result flows for compiled calls. Sources: src/go/types/call.go, src/cmd/compile/internal/types2/call.go, src/cmd/compile/internal/escape/call.go
Relevant Source Files
src/go/types/call.go- Public type-checker implementation for call and selector expressions, including function instantiation and argument-count/type diagnostics used by tools that analyze Go source.src/cmd/compile/internal/types2/call.go- Compiler-internal counterpart for checking calls and selectors during compilation, aligned with the language rules enforced by the compiler front end.src/cmd/compile/internal/escape/call.go- Escape-analysis handling for call expressions after type checking, including static callee discovery, receiver handling, argument flow, and result flow.src/internal/types/errors/code_string.go- Generated string names for type-checker error codes such asBrokenImport,UndeclaredImportedName,WrongArgCount,InvalidCall, andWrongTypeArgCount.src/compress/flate/huffman_code.go- Example of an ordinary importable package file in the standard library, showing package-level declarations and helper functions that become available through imports within that package boundary.src/cmd/internal/obj/s390x/condition_code.go- Example of an internal package with exported names such asCCMask, illustrating that export from a package and accessibility from an import path are related but not identical concerns.
Tutorial Flow
Start with two sibling directories: one for the module that provides a package and one for the module that calls it. In the tutorial shape, greetings contains a package that exports Hello, and hello contains a command package that imports and calls it. A module is declared with go mod init, and a package is selected in source code with an import path. The command module must use package main because it builds an executable, while the imported package can use its own package name and expose only identifiers whose names begin with an upper-case letter.
cd ..
mkdir hello
cd hello
go mod init example.com/helloCreate hello.go in the caller module. The import declaration gives the file access to the standard library package fmt and to the package whose import path is example.com/greetings. The selector expression greetings.Hello then refers to the exported identifier Hello from the imported package. If that identifier is not exported, not present, or not in scope under that package name, the type checker reports an imported-name or selector error rather than letting the build proceed.
package main
import (
"fmt"
"example.com/greetings"
)
func main() {
message := greetings.Hello("Gladys")
fmt.Println(message)
}For local tutorial development, the caller module also needs a way to map example.com/greetings to the sibling directory before the library has been published. The official tutorial uses module-aware dependency tracking and local replacement so the import path remains the same as the published path while the build uses local source. Conceptually, the import path names the package API the caller wants, and module metadata tells the go command where that package’s module version or local replacement comes from.
Core Primitives
The central primitive in this workflow is the import path. It is the string in the import declaration and acts as the package identity used by source files. A module path is a prefix for packages in the module, and a package path is formed from that module path plus a subdirectory path. The caller does not import a module as a whole; it imports a package contained in a module. That distinction is important when a module contains multiple packages or when a repository contains more than one module.
A second primitive is the package name. The package name is the identifier used in selectors inside the importing file, such as greetings.Hello. Most packages use a package name related to the last element of the import path, but the compiler type-checks selectors against the imported package’s declarations, not merely against the textual path. Standard-library code such as compress/flate demonstrates normal package organization: the file declares package flate, defines package-local constants, types, variables, and functions, and those declarations are compiled together as the package. Sources: src/compress/flate/huffman_code.go
A third primitive is export. In Go, a declaration with an upper-case name is exported from its package. The s390x condition-code source shows exported declarations such as CCMask, Never, Equal, and methods on CCMask, but it is under an internal directory. That placement illustrates an important boundary: an identifier may be exported from its package, yet the import path may still be restricted by Go’s internal-package visibility rules. When calling code from another module, use the public package paths the module author intends callers to import. Sources: src/cmd/internal/obj/s390x/condition_code.go
System-to-Code Mapping
After the go command has resolved imports and loaded packages, go/types and the compiler’s types2 implementation check call and selector expressions. The call checker handles function instantiation, counts type arguments, verifies Go version gates for generic function instantiation, and reports structured errors when the call form is invalid. This is why mistakes such as too many arguments, too many type arguments, or trying to call a non-function surface as compile-time diagnostics instead of runtime failures. Sources: src/go/types/call.go, src/cmd/compile/internal/types2/call.go
The generated error-code string table shows the diagnostic vocabulary used by the type-checking layer. For a tutorial caller, the most relevant names include BrokenImport when an import cannot be resolved, UndeclaredImportedName when code refers to a missing name in an imported package, WrongArgCount when the call does not match the function signature, InvalidCall when the expression is not callable, and WrongTypeArgCount or CannotInferTypeArgs for generic calls. These names are internal diagnostic categories, but they map closely to the kinds of user-facing errors a new module author will see. Sources: src/internal/types/errors/code_string.go
Compilation continues past type checking into optimization and code generation. Escape analysis treats a call expression as a place where arguments, receivers, and results can flow between stack and heap locations. The compiler source distinguishes direct function calls, interface calls, receiver arguments for method calls, unknown callees, and statically known callees. This is not something a tutorial user normally configures, but it explains why the compiler needs exact call structure after import resolution: the call is both a type-system event and a data-flow event. Sources: src/cmd/compile/internal/escape/call.go
Execution Flow
A practical run of the tutorial has four phases. First, create the caller module with go mod init example.com/hello. Second, write hello.go with package main, imports, and a call to greetings.Hello. Third, connect the dependency, usually by editing module metadata through go mod edit -replace for the local sibling module and then running go mod tidy to add the requirement. Fourth, run the program with go run . and let the toolchain resolve the imported package, type-check the call, compile both packages, and execute the command.
go mod edit -replace example.com/greetings=../greetings
go mod tidy
go run .When the call succeeds, the important contract is simple: the imported package must be resolvable, the selected identifier must be exported and declared by that package, and the function call must match the signature. If Hello expects a string and returns a string, the caller can bind the result and pass it to fmt.Println. If the function later changes to return an additional error value, the caller must be updated because assignment and call-result counts are checked statically.
API Components and Diagnostics
The compact reference for this tutorial is small but precise. The caller file uses package main, an import declaration containing "example.com/greetings", a selector expression greetings.Hello, and a call expression greetings.Hello("Gladys"). The module commands are go mod init to create the caller module, go mod edit -replace to point an import path’s module to local source during development, go mod tidy to synchronize requirements, and go run . to load, build, and execute the command package.
Typical failures fall into distinct layers. A module-loading failure means the import path cannot be found from module requirements, replacements, or configured download sources. A package/name failure means the package was found but the selected identifier is not available under that package name. A call failure means the selected expression exists but is not callable or is called with the wrong arguments or type arguments. Separating those layers makes troubleshooting faster: inspect go.mod for resolution problems, package declarations and exported names for selector problems, and the function signature for call problems.
Next, continue with the module tutorial steps that add error returns and tests, or read the modules overview and go.mod reference to understand how requirements, replacements, and versions control cross-module imports.