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 once or #ifndef) prevent double inclusion
  • Circular includes cause compilation errors — use forward declarations to break cycles
  • Header-only libraries (.h with 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 tidy removes unused dependencies from go.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.lang package is imported automatically (no need to import String, 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 }
}
  • pub controls visibility: pub, pub(crate), pub(super), private (default)
  • use is purely a convenience alias — it doesn't affect compilation units
Loading...