Search⌘ K
AI Features

Definition of Structs

Explore the definition of structs in the D programming language and understand how they allow you to create new types by combining related variables. Learn to use structs to simplify function parameters, return multiple values, and organize complex data effectively in your programs.

Chapter overview

Fundamental types are not suitable to represent higher-level concepts. For example, although a value of type int is suitable to represent the hour of the day, two int variables would be more suitable together to represent a point in time: one for the hour and the other for the minute.

Structs are the features that allow defining new types by combining already existing types. The new type is defined by the struct keyword. By this definition, structs are user-defined types. Most of the content of this chapter is directly applicable to classes as well, especially the concept of combining existing types to define a new type.

This chapter covers only the basic features of structs. You will see more of structs in the following lessons:

To understand how useful structs are, let’s define a function addDuration():

void addDuration(int startHour, int startMinute,
                 int durationHour, int durationMinute,
                 out int resultHour, out int resultMinute) { 
    resultHour = startHour + durationHour;
    resultMinute = startMinute + 
    durationMinute; resultHour += resultMinute / 60;

    resultMinute %= 60;
    resultHour %= 24; 
}

Note: We will ignore the in, out, and unittest blocks in this chapter to keep the code samples short.

Although the function above clearly takes six parameters, it is conceptually taking only three bits of information for the starting time, the duration, and the result (when the three pairs of parameters are considered).

Structs definition

The struct keyword defines a new type by combining variables that are related in some way:

D
struct TimeOfDay {
int hour;
int minute;
}
void main() {
}

The code above defines a new type named TimeOfDay, which consists of two variables named hour and minute. That definition allows the new TimeOfDay type to be used in the program just like any other type. The following code demonstrates how similar its use is to a fundamental type:

D
struct TimeOfDay {
int hour;
int minute;
}
void main() {
int number; // a variable
int otherNumber;
number = otherNumber; // taking the value of otherNumber
TimeOfDay time; // an object
TimeOfDay otherTime;
time = otherTime; // taking the value of otherTime
}

The syntax of struct definition is the following:

struct TypeName {
    // ... member variables and functions ...
}

The variables that a struct combines are called its members. According to this definition, TimeOfDay has two members: hour and minute.

struct defines a type, not a variable

There is an important distinction here. The curly brackets of struct definitions may give the incorrect impression that the struct members start and end their lives inside that scope. This is not true.

Member definitions are not variable definitions:

D
struct TimeOfDay {
int hour; // ← Not a variable; will become a part of
// a struct variable used in the program.
int minute; // ← Not a variable; will become a part of
// a struct variable used in the program.
}
void main() {
}

The definition of a struct determines the types and the names of the members that the objects of that struct will have. Those member variables will be constructed as parts of TimeOfDay objects that take part in the program:

D
struct TimeOfDay {
int hour;
int minute;
}
void main() {
TimeOfDay bedTime; // This object contains its own hour
// and minute member variables.
TimeOfDay wakeUpTime; // This object contains its own hour
// and minute member variables as
// well. The member variables of
// this object are not related to
// the member variables of the
// previous object.
}

The variables of struct and class types are called objects.

Coding convenience

Being able to combine the concepts of hour and minute together as a new type is a great convenience. For example, the function above can be rewritten in a more meaningful way by taking three TimeOfDay parameters instead of the existing six int parameters:

void addDuration(TimeOfDay start, TimeOfDay duration, out TimeOfDay result) {
    // ...
}

Note: It is not normal to add two variables that represent two points in time. For example, it is meaningless to add the lunchtime 12:00 to the breakfast time 7:30. It would make more sense to define another type, appropriately called Duration, and to add objects of that type to TimeOfDay objects. Despite this design flaw, we will continue using only TimeOfDay objects in this chapter and introduce Duration in a later chapter.

As you know, functions return up-to a single value. That is precisely the reason why the earlier definition of addDuration() was taking two out parameters: It could not return the hour and minute information as a single value.

Structs remove this limitation as well. Since multiple values can be combined as a single struct type, functions can return an object of such a struct, effectively returning multiple values at once. addDuration() can now be defined as returning its result:

TimeOfDay addDuration(TimeOfDay start, TimeOfDay duration) {
    TimeOfDay result;
    // ...
    return result;
}

Consequently, addDuration() now becomes a function that produces a value, as opposed to being a function that has side effects. As you know, producing results is preferred over having side effects.

Structs can be members of other structs. For example, the following struct has two TimeOfDay members:

D
struct TimeOfDay {
int hour;
int minute;
}
struct Meeting {
string topic;
size_t attendanceCount;
TimeOfDay start;
TimeOfDay end;
}
void main() {
}

In turn, Meeting can be a member of another struct. Assuming that there is also the Meal struct:

D
struct TimeOfDay {
int hour;
int minute;
}
struct Meeting {
string topic;
size_t attendanceCount;
TimeOfDay start;
TimeOfDay end;
}
struct Meal {
string menu;
}
struct DailyPlan {
Meeting projectMeeting;
Meal lunch;
Meeting budgetMeeting;
}
void main() {
}

Accessing the members

Struct members are used like any other variable. The only difference is that the actual struct variable name and a dot must be specified before the name of the member:

start.hour = 10;

The line above assigns the value 10 to the hour member of the start object.

Let’s rewrite the addDuration() function with what we have seen so far:

D
struct TimeOfDay {
int hour;
int minute;
}
TimeOfDay addDuration(TimeOfDay start,TimeOfDay duration) {
TimeOfDay result;
result.minute = start.minute + duration.minute;
result.hour = start.hour + duration.hour;
result.hour += result.minute / 60;
result.minute %= 60;
result.hour %= 24;
return result;
}
void main() {
}

Notice that the names of the variables are much shorter in this version of the function: start, duration, and result. Additionally, instead of using complex names like startHour, it is possible to access struct members through their respective struct variables as in start.hour.

Here is a code that uses the new addDuration() function. Given the start time and the duration, the following code calculates when a class period at a school would end:

D
import std.stdio;
struct TimeOfDay {
int hour;
int minute;
}
TimeOfDay addDuration(TimeOfDay start,TimeOfDay duration) {
TimeOfDay result;
result.minute = start.minute + duration.minute;
result.hour = start.hour + duration.hour;
result.hour += result.minute / 60;
result.minute %= 60;
result.hour %= 24;
return result;
}
void main() {
TimeOfDay periodStart;
periodStart.hour = 8;
periodStart.minute = 30;
TimeOfDay periodDuration;
periodDuration.hour = 1;
periodDuration.minute = 15;
immutable periodEnd = addDuration(periodStart, periodDuration);
writefln("Period end: %s:%s", periodEnd.hour, periodEnd.minute);
}

Above, main() has been written only by what we have seen so far. We will make this code even shorter and cleaner soon.