Mojo File IO

File I/O (Input/Output) lets your program read data from files on disk and write results back. Reading configuration files, processing datasets, saving logs, and generating reports all require file I/O. Mojo provides the open() function and a FileHandle type for this purpose.

The Filing Cabinet Analogy

  Filing Cabinet = Disk storage
  Drawer label   = File path ("data/scores.txt")
  Open drawer    = open("data/scores.txt", "r")
  Read papers    = file.read()
  Close drawer   = file.close()

  Always close the drawer after you finish.
  An open file holds system resources the OS needs back.

Opening and Reading a File

fn main() raises:
    var file = open("hello.txt", "r")   # "r" = read mode
    var content = file.read()
    file.close()
    print(content)

The second argument to open() is the mode string:

Mode | Meaning
-----|-------------------------------------------------------
"r"  | Read text (file must exist)
"w"  | Write text (creates or overwrites file)
"a"  | Append text (creates if missing, adds to end)
"rb" | Read binary bytes
"wb" | Write binary bytes

Writing to a File

fn main() raises:
    var file = open("output.txt", "w")
    file.write("Line 1: Hello from Mojo\n")
    file.write("Line 2: File I/O works!\n")
    file.write("Line 3: Done.\n")
    file.close()
    print("File written successfully")

Opening in "w" mode creates the file if it does not exist and erases all existing content if it does. Use "a" (append) to add content without erasing.

Using with for Automatic Close

The with statement opens a file and automatically closes it when the block ends — even if an error occurs inside the block. This is the recommended approach because it prevents resource leaks.

fn main() raises:
    # Write phase
    with open("numbers.txt", "w") as f:
        for i in range(1, 6):
            f.write(String(i) + "\n")
    # File closed automatically here

    # Read phase
    with open("numbers.txt", "r") as f:
        var data = f.read()
        print(data)

Output:

1
2
3
4
5

Reading Line by Line

For large files, reading one line at a time avoids loading the entire file into memory at once.

fn main() raises:
    with open("numbers.txt", "r") as f:
        var line = f.readline()
        while line != "":
            var trimmed = line.strip()
            if trimmed != "":
                print("Read:", trimmed)
            line = f.readline()

Output:

Read: 1
Read: 2
Read: 3
Read: 4
Read: 5
readline() behavior:
  First call  → "1\n"
  Second call → "2\n"
  ...
  After last line → ""  (empty string signals end of file)

Appending to an Existing File

fn main() raises:
    # First run creates file with initial content
    with open("log.txt", "w") as f:
        f.write("Session started\n")

    # Subsequent runs append without erasing
    with open("log.txt", "a") as f:
        f.write("New event logged\n")

    # Read back the full content
    with open("log.txt", "r") as f:
        print(f.read())

Output:

Session started
New event logged

CSV Processing Pattern

CSV files are the most common format for tabular data. Mojo reads them as text and you split lines on commas.

fn main() raises:
    # Write a sample CSV
    with open("students.csv", "w") as f:
        f.write("name,score,grade\n")
        f.write("Alice,95,A\n")
        f.write("Bob,82,B\n")
        f.write("Carol,74,C\n")

    # Read and parse it
    with open("students.csv", "r") as f:
        var header = f.readline()   # skip header
        var line = f.readline()
        while line != "":
            var trimmed = line.strip()
            if trimmed != "":
                # Manual split on comma
                var parts = trimmed.split(",")
                print(parts[0], "scored", parts[1], "→ grade", parts[2])
            line = f.readline()

Output:

Alice scored 95 → grade A
Bob scored 82 → grade B
Carol scored 74 → grade C

Checking if a File Exists

from os import path

fn main():
    if path.exists("config.txt"):
        print("Config file found")
    else:
        print("Config file missing — using defaults")

File Error Handling

File operations fail when the file is missing, permissions are denied, or the disk is full. Always wrap file operations in try/except for production code.

fn read_config(path: String) raises -> String:
    try:
        with open(path, "r") as f:
            return f.read()
    except e:
        raise Error("Failed to read " + path + ": " + str(e))

fn main():
    try:
        var config = read_config("settings.txt")
        print(config)
    except e:
        print("Using defaults:", str(e))

Key Takeaways

Open files with open(path, mode) where mode is "r", "w", or "a". Always close files — the with statement handles this automatically. Use read() for the entire file content, readline() for line-by-line processing of large files. Append mode adds to existing content without erasing it. Wrap file operations in try/except to handle missing files and permission errors gracefully. Check file existence with path.exists() before opening when the file is optional.

Leave a Comment

Your email address will not be published. Required fields are marked *