Programming in D – Tutorial and Reference
Ali Çehreli

Other D Resources

Files

We have seen in the previous chapter that the standard input and output streams can be redirected to and from files and other programs with the >, <, and | operators on the terminal. Despite being very powerful, these tools are not suitable in every situation because in many cases programs can not complete their tasks simply by reading from their input and writing to their output.

For example, a program that deals with student records may use its standard output to display the program menu. Such a program would need to write the student records to an actual file instead of to stdout.

In this chapter, we will cover reading from and writing to files of file systems.

Fundamental concepts

Files are represented by the File struct of the std.stdio module. Since I haven't introduced structs yet, we will have to accept the syntax of struct construction as is for now.

Before getting to code samples we have to go through fundamental concepts about files.

The producer and the consumer

Files that are created on one platform may not be readily usable on other platforms. Merely opening a file and writing data to it may not be sufficient for that data to be available on the consumer's side. The producer and the consumer of the data must have already agreed on the format of the data that is in the file. For example, if the producer has written the id and the name of the student records in a certain order, the consumer must read the data back in the same order.

Additionally, the code samples below do not write a byte order mark (BOM) to the beginning of the file. This may make your files incompatible with systems that require a BOM. (The BOM specifies in what order the UTF code units of characters are arranged in a file.)

Access rights

File systems present files to programs under certain access rights. Access rights are important for both data integrity and performance.

When it comes to reading, allowing multiple programs to read from the same file can improve performance, because the programs will not have to wait for each other to perform the read operation. On the other hand, when it comes to writing, it is often beneficial to prevent concurrent accesses to a file, even when only a single program wants to write to it. By locking the file, the operating system can prevent other programs from reading partially written files, from overwriting each other's data and so on.

Opening a file

The standard input and output streams stdin and stdout are already open when programs start running. They are ready to be used.

On the other hand, normal files must first be opened by specifying the name of the file and the access rights that are needed. As we will see in the examples below, creating a File object is sufficient to open the file specified by its name:

    File file = File("student_records", "r");
Closing a file

Any file that has been opened by a program must be closed when the program finishes using that file. In most cases the files need not be closed explicitly; they are closed automatically when File objects are terminated automatically:

if (aCondition) {

    // Assume a File object has been created and used here.
    // ...

} // ← The actual file would be closed automatically here
  //   when leaving this scope. No need to close explicitly.

In some cases a file object may need to be re-opened to access a different file or the same file with different access rights. In such cases the file must be closed and re-opened:

    file.close();
    file.open("student_records", "r");
Writing to and reading from files

Since files are character streams, input and output functions writeln, readf, etc. are used exactly the same way with them. The only difference is that the name of the File variable and a dot must be typed:

    writeln("hello");        // writes to the standard output
    stdout.writeln("hello"); // same as above
    file.writeln("hello");   // writes to the specified file
eof() to determine the end of a file

The eof() member function determines whether the end of a file has been reached while reading from a file. It returns true if the end of the file has been reached.

For example, the following loop will be active until the end of the file:

    while (!file.eof()) {
        /* ... */
    }
The std.file module

The std.file module contains functions and types that are useful when working with contents of directories. For example, exists can be used to determine whether a file or a directory exists on the file systems:

import std.file;

// ...

    if (exists(fileName)) {
        // there is a file or directory under that name

    } else {
        // no file or directory under that name
    }
std.stdio.File struct

The File struct is included in the std.stdio module. To use it you specify the name of the file you want to open and the desired access rights, or mode. It uses the same mode characters that are used by fopen of the C programming language:

 Mode  Definition
rread access
the file is opened to be read from the beginning
r+read and write access
the file is opened to be read from and written at the beginning
wwrite access
if the file does not exist, it is created as empty
if the file already exists, its contents are cleared
w+read and write access
if the file does not exist, it is created as empty
if the file already exists, its contents are cleared
aappend access
if the file does not exist, it is created as empty
if the file already exists, its contents are preserved and it is opened to be written at the end
a+read and append access
if the file does not exist, it is created as empty
if the file already exists, its contents are preserved and the file is opened to be read from the beginning and written at the end

A 'b' character may be added to the mode string, as in "rb". This may have an effect on platforms that support the binary mode, but it is ignored on all POSIX systems.

Writing to a file

The file must have been opened in one of the write modes first:

import std.stdio;

void main()
{
    File file = File("student_records", "w");

    file.writeln("Name  : ", "Zafer");
    file.writeln("Number: ", 123);
    file.writeln("Class : ", "1A");
}

As you remember from the Strings chapter, the type of literals like "student_records" is string, consisting of immutable characters. For that reason, it is not possible to construct File objects by using mutable text to specify the file name (e.g. char[]). When needed, call the .idup property of the mutable string to get an immutable copy.

The program above creates or overwrites the contents of a file named student_records in the directory that it has been started under (in the program's working directory).

Note: File names can contain any character that is legal for that file system. To be portable, I will use only the commonly supported ASCII characters.

Reading from a file

To read from a file the file must first have been opened in one of the read modes:

import std.stdio;
import std.string;

void main()
{
    File file = File("student_records", "r");

    while (!file.eof()) {
        string line = chomp(file.readln());
        writeln("read line -> |", line);
    }
}

The program above reads all of the lines of the file named student_records and prints those lines to its standard output.

Exercise

Write a program that takes a file name from the user, opens that file, and writes all of the non-empty lines of that file to another file. The name of the new file can be based on the name of the original file. For example, if the original file is foo.txt, the new file can be foo.txt.out.