Back to Tutorials

Rust Modules vs Python Packages

10 min readBeginner

Code organization is crucial in any programming language. Let's explore how Rust's module system compares to Python's package structure and learn the best practices for organizing your Rust projects.

Python Package Structure

In Python, you organize code using files and directories. Each directory with an__init__.py file becomes a package:

Python Project Structure:
my_project/
├── __init__.py
├── main.py
├── utils/
│   ├── __init__.py
│   ├── helpers.py
│   └── math_ops.py
└── models/
    ├── __init__.py
    ├── user.py
    └── product.py

Basic Module Structure

Python
# utils/math_ops.py
def add(a, b):
    return a + b

def multiply(a, b):
    return a * b

# utils/__init__.py
from .math_ops import add, multiply

# main.py
from utils import add, multiply
# or
from utils.math_ops import add

result = add(5, 3)
Rust
// src/utils/math_ops.rs
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

pub fn multiply(a: i32, b: i32) -> i32 {
    a * b
}

// src/utils/mod.rs
pub mod math_ops;
pub use math_ops::{add, multiply};

// src/main.rs
mod utils;
use utils::{add, multiply};

fn main() {
    let result = add(5, 3);
}

Rust Module System

Rust uses a different approach. Instead of relying on the file system alone, Rust has explicit module declarations:

Rust Project Structure:
my_project/
├── Cargo.toml
└── src/
    ├── main.rs
    ├── utils/
    │   ├── mod.rs
    │   └── math_ops.rs
    └── models/
        ├── mod.rs
        ├── user.rs
        └── product.rs

Module Declaration and Visibility

One key difference is that Rust requires explicit module declarations and visibility controls:

Visibility Control

Python
# Python: Everything is public by convention
# utils/helpers.py
def public_function():
    return "I'm public"

def _private_function():  # Convention: starts with _
    return "I'm private by convention"

# main.py
from utils.helpers import public_function, _private_function
# Both work, privacy is just convention
Rust
// Rust: Explicit visibility control
// src/utils/helpers.rs
pub fn public_function() -> &'static str {
    "I'm public"
}

fn private_function() -> &'static str {  // No 'pub' = private
    "I'm private"
}

// src/main.rs
mod utils;
use utils::helpers::public_function;
// use utils::helpers::private_function;  // ❌ Error! Not public

Importing and Using Code

Both languages have ways to import and use code from other modules, but the syntax and behavior differ:

Import Syntax Comparison

Python
# Python imports
import json
import os.path
from collections import defaultdict
from typing import List, Dict

# Relative imports
from .utils import helper_function
from ..models import User

# Aliasing
import numpy as np
from datetime import datetime as dt
Rust
// Rust use statements
use std::collections::HashMap;
use std::fs;
use std::path::Path;

// Multiple imports from same module
use std::io::{self, Read, Write};

// Relative imports (within your crate)
use crate::utils::helper_function;
use super::models::User;

// Aliasing
use std::collections::HashMap as Map;

Re-exports and Module Organization

Both languages support re-exporting symbols to create cleaner APIs:

Re-exports

Python
# Python re-exports
# mylib/__init__.py
from .core import CoreClass
from .utils import helper_function
from .advanced import AdvancedFeature

# Users can import directly from mylib
# from mylib import CoreClass, helper_function

# Or create aliases
from .core import CoreClass as Core
Rust
// Rust re-exports
// src/lib.rs
mod core;
mod utils;
mod advanced;

// Re-export for easier access
pub use core::CoreStruct;
pub use utils::helper_function;
pub use advanced::AdvancedFeature;

// Users can import directly
// use mylib::{CoreStruct, helper_function};

// Or create aliases
pub use core::CoreStruct as Core;

Inline Modules vs Separate Files

Rust allows you to define modules inline or in separate files, giving you more flexibility than Python:

Inline vs File Modules

Python
# Python: Always separate files for modules
# You can't define a module inline in Python
# Each .py file is a module

# utils.py
def helper():
    pass

# main.py
import utils
utils.helper()
Rust
// Rust: Inline modules
mod utils {
    pub fn helper() {
        println!("Helper function");
    }
    
    mod private_submodule {
        pub fn internal_function() {
            println!("Internal");
        }
    }
}

fn main() {
    utils::helper();
}

// Or separate files (like Python)
// mod utils;  // Looks for utils.rs or utils/mod.rs

Best Practices Comparison

Python Best Practices

  • • Use __init__.py to control package API
  • • Follow PEP 8 naming conventions
  • • Use relative imports within packages
  • • Keep modules focused and cohesive
  • • Use __all__ to control from module import *

Rust Best Practices

  • • Use mod.rs or lib.rs to control module API
  • • Follow Rust naming conventions (snake_case)
  • • Use pub use for re-exports
  • • Keep modules focused and cohesive
  • • Use crate:: for absolute paths within your crate

Key Takeaways

  • Explicit vs Implicit: Rust requires explicit module declarations, Python uses file system
  • Visibility: Rust has compile-time privacy, Python uses conventions
  • Re-exports: Both support clean APIs through re-exports
  • Flexibility: Rust allows inline modules, Python requires separate files
  • Organization: Both support hierarchical code organization

Pro Tip: Start with Rust's module system by thinking about your Python package structure, then add explicit mod declarations and pub keywords where needed. The compiler will guide you through any issues!