Programming Languages - Imports & Modules
Understanding how each language handles imports and module systems is fundamental to structuring large codebases.
Concepts
| Concept | Description |
|---|---|
| Module | A unit of code organization (file, package, or namespace) |
| Import | Making symbols from another module available in the current scope |
| Namespace | A named scope that prevents symbol collisions |
| Package | A collection of related modules (Go, Java, Python) |
C++
C++ uses #include to textually paste header file contents. There is no true module system until C++20.
// Standard library
#include <string> // std::string
#include <vector> // std::vector
#include <utility> // std::move, std::pair
#include <functional> // std::function
#include <memory> // std::unique_ptr, std::shared_ptr
#include <algorithm> // std::sort, std::find
#include <iostream> // std::cout
// User-defined header
#include "mylib.h"
#include "../utils/helper.h"
C++20 Modules (new, compiler support varies):
import std.core; // replaces many #include directives
Key pitfalls:
- Include guards (
#pragma onceor#ifndef) prevent double inclusion - Circular includes cause compilation errors — use forward declarations to break cycles
- Header-only libraries (
.hwith implementation) can bloat compile times
Go
Go uses package-level imports. The import path is the module path + directory path relative to module root.
import (
// Standard library
"fmt"
"os"
"strings"
"sync"
"context"
// Third-party (requires `go get` first)
"github.com/gin-gonic/gin"
// Aliased import (avoid name collision)
myfmt "github.com/myorg/fmt"
// Blank import (side effects only, e.g., register a driver)
_ "github.com/lib/pq"
// Dot import (imports all exported names directly — avoid in production)
. "math"
)
- Go enforces that every import is used — unused imports are compile errors
- Package name = last element of import path by convention (
"net/http"→http) go mod tidyremoves unused dependencies fromgo.mod
Java
Java imports are resolved by the JVM classpath. Wildcard imports (*) are legal but discouraged in production code.
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
// Wildcard (imports all public types in the package)
import java.util.*;
// Static import (import static methods/fields directly)
import static java.lang.Math.PI;
import static java.lang.Math.sqrt;
- The
java.langpackage is imported automatically (no need to importString,Integer, etc.) - Packages map to directory structure on disk
Python
Python's import system is flexible. Modules are .py files; packages are directories with __init__.py.
# Absolute import (preferred)
import os
import sys
from pathlib import Path
# Import specific names
from collections import defaultdict, Counter
from typing import List, Dict, Optional
# Aliased import
import numpy as np
import pandas as pd
# Relative import (within a package)
from . import sibling_module
from ..utils import helper
# Wildcard import (avoid — pollutes namespace)
from os.path import *
__init__.py: Makes a directory a package. Can re-export symbols to simplify the public API:
# mypackage/__init__.py
from .core import MyClass
from .utils import helper_func
JavaScript / TypeScript
JS has two module systems: CommonJS (Node.js legacy) and ES Modules (modern standard).
ES Modules (ESM):
// Default import
import React from 'react';
// Named imports
import { readFileSync, writeFileSync } from 'fs';
// Namespace import (all exports as object)
import * as d3 from 'd3';
// Dynamic import (lazy-loading, returns Promise)
const module = await import('./heavy-module.js');
// Re-export
export { foo } from './foo.js';
export * from './bar.js';
CommonJS (legacy Node.js):
const fs = require('fs');
const { join, resolve } = require('path');
module.exports = { myFunc };
Key difference: ESM is statically analyzed (enables tree-shaking); CJS is dynamic (evaluated at runtime).
Rust
Rust's module system is built around mod declarations and use paths.
// Bring into scope from standard library
use std::collections::HashMap;
use std::io::{self, Read, Write};
use std::sync::{Arc, Mutex};
// External crate (listed in Cargo.toml)
use serde::{Deserialize, Serialize};
// Glob import (use sparingly)
use std::prelude::*;
// Nested paths (reduce repetition)
use std::{
fmt,
fs::File,
path::PathBuf,
};
Modules are declared with mod:
// Declares a submodule (looks for src/utils.rs or src/utils/mod.rs)
mod utils;
// Inline module
mod config {
pub fn default_timeout() -> u64 { 30 }
}
pubcontrols visibility:pub,pub(crate),pub(super), private (default)useis purely a convenience alias — it doesn't affect compilation units