Structs - D Programming Language

Menu

- Learn
- Documentation
  - Language Reference
  - Library Reference
  - Command-line Reference
  - Feature Overview
  - Articles
- Downloads
- Packages
- Community
  - Blog
  - Orgs using D
  - Twitter
  - Calendar
  - Discord (community-run)
  - Forums
  - IRC
  - Wiki
  - Issues
  - Contributing
  - Contributors
  - Foundation
  - Security Team
  - Donate
  - Sponsors
- Resources
  - Tour
  - Books
  - Tutorials
  - Tools
  - Editors
  - IDEs
  - run.dlang.io
  - Visual D
  - Acknowledgments
  - D Style
  - Glossary
  - Sitemap

Search
Report a bug
If you spot a problem with this page, click here to create a Bugzilla issue.
Improve this page
Quickly fork, edit online, and submit a pull request for this page. Requires a signed-in GitHub account. This works well for small changes. If you'd like to make larger changes you may want to consider using a local clone.

# Structs

As has been mentioned several times earlier in the book, 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 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 feature that allow defining new types by combining already existing other 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 is exactly the same for them.

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

- Member Functions
- const ref Parameters and const Member Functions
- Constructor and Other Special Functions
- Operator Overloading
- Encapsulation and Protection Attributes
- Properties
- Contract Programming for Structs and Classes
- foreach with Structs and Classes

To understand how useful structs are, let's take a look at the `addDuration()` function that we had defined earlier in the `assert` and `enforce` chapter. The following definition is from the exercise solution of that chapter:

    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: I 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, when the three pairs of parameters are considered, it is conceptually taking only three bits of information for the starting time, the duration, and the result.

##### Definition

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

    struct TimeOfDay {
        int hour;
        int minute;
    }

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 an `int`'s:

        int number;            // a variable
        number = otherNumber;  // taking the value of otherNumber

        TimeOfDay time;        // an object
        time = otherTime;      // taking the value of otherTime

The syntax of `struct` definition is the following:

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

We will see member functions in later chapters.

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: Especially after the Name Scope and Lifetimes and Fundamental Operations chapters, the curly brackets of `struct` definitions may give the wrong impression that the struct members start and end their lives inside that scope. This is not true.

Member definitions are not variable definitions:

    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.
    }

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:

        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.

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 lunch time 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, I will continue using only `TimeOfDay` objects in this chapter and introduce `Duration` in a later chapter.

As you remember, functions return up-to a single value. That has precisely been 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) {
        // ...
    }

As a consequence, `addDuration()` now becomes a function that produces a value, as opposed to being a function that has side effects. As you would remember from the Functions chapter, producing results is preferred over having side effects.

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

    struct Meeting {
        string    topic;
        size_t    attendanceCount;
        TimeOfDay start;
        TimeOfDay end;
    }

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

    struct DailyPlan {
        Meeting projectMeeting;
        Meal    lunch;
        Meeting budgetMeeting;
    }

##### Accessing the members

Struct members are used like any other variable. The only difference is that the actual struct variable 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:

    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;
    }

Notice that the names of the variables are now 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:

    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);
    }

The output:

    Period end: 9:45

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

##### Construction

The first three lines of `main()` are about constructing the `periodStart` object and the next three lines are about constructing the `periodDuration` object. In each three lines of code first an object is being defined and then its hour and minute values are being set.

In order for a variable to be used in a safe way, that variable must first be constructed in a consistent state. Because construction is so common, there is a special construction syntax for struct objects:

        TimeOfDay periodStart = TimeOfDay(8, 30);
        TimeOfDay periodDuration = TimeOfDay(1, 15);

The values are automatically assigned to the members in the order that they are specified: Because `hour` is defined first in the `struct`, the value 8 is assigned to `periodStart.hour` and 30 is assigned to `periodStart.minute`.

As we have seen in the Type Conversions chapter, the construction syntax can be used for other types as well:

        auto u = ubyte(42);    // u is a ubyte
        auto i = int(u);       // i is an int

###### Constructing objects as `immutable`

Being able to construct the object by specifying the values of its members at once makes it possible to define objects as `immutable`:

        immutable periodStart = TimeOfDay(8, 30);
        immutable periodDuration = TimeOfDay(1, 15);

Otherwise it would not be possible to mark an object first as `immutable` and then modify its members:

        immutable TimeOfDay periodStart;
        periodStart.hour = 8;      // ← derleme HATASI
        periodStart.minute = 30;   // ← derleme HATASI

###### Trailing members need not be specified

There may be fewer values specified than the number of members. In that case, the remaining members are initialized by the `.init` values of their respective types.

The following program constructs `Test` objects each time with one less constructor parameter. The `assert` checks indicate that the unspecified members are initialized automatically by their `.init` values. (The reason for needing to call `isNaN()` is explained after the program):

    import std.math;

    struct Test {
        char   c;
        int    i;
        double d;
    }

    void main() {
        // The initial values of all of the members are specified
        auto t1 = Test('a', 1, 2.3);
        assert(t1.c == 'a');
        assert(t1.i == 1);
        assert(t1.d == 2.3);

        // Last one is missing
        auto t2 = Test('a', 1);
        assert(t2.c == 'a');
        assert(t2.i == 1);
        assert(isNaN(t2.d));

        // Last two are missing
        auto t3 = Test('a');
        assert(t3.c == 'a');
        assert(t3.i == int.init);
        assert(isNaN(t3.d));

        // No initial value specified
        auto t4 = Test();
        assert(t4.c == char.init);
        assert(t4.i == int.init);
        assert(isNaN(t4.d));

        // The same as above
        Test t5;
        assert(t5.c == char.init);
        assert(t5.i == int.init);
        assert(isNaN(t5.d));
    }

As you would remember from the Floating Point Types chapter, the initial value of `double` is `double.nan`. Since the `.nan` value is unordered, it is meaningless to use it in equality comparisons. That is why calling `std.math.isNaN` is the correct way of determining whether a value equals to `.nan`.

###### Specifying default values for members

It is important that member variables are automatically initialized with known initial values. This prevents the program from continuing with indeterminate values. However, the `.init` value of their respective types may not be suitable for every type. For example, `char.init` is not even a valid value.

The initial values of the members of a struct can be specified when the struct is defined. This is useful for example to initialize floating point members by `0.0`, instead of the mostly-unusable `.nan`.

The default values are specified by the assignment syntax as the members are defined:

    struct Test {
        char   c = 'A';
        int    i = 11;
        double d = 0.25;
    }

Please note that the syntax above is not really assignment. The code above merely determines the default values that will be used when objects of that struct are actually constructed later in the program.

For example, the following `Test` object is being constructed without any specific values:

        Test t;  // no value is specified for the members
        writefln("%s,%s,%s", t.c, t.i, t.d);

All of the members are initialized by their default values:

    A,11,0.25

###### Constructing by the `{}` syntax

Struct objects can also be constructed by the following syntax:

        TimeOfDay periodStart = { 8, 30 };

Similar to the earlier syntax, the specified values are assigned to the members in the order that they are specified. The trailing members get their default values.

This syntax is inherited from the C programming language:

        auto periodStart = TimeOfDay(8, 30);    // ← regular
        TimeOfDay periodEnd = { 9, 30 };        // ← C-style

This syntax allows designated initializers. Designated initializers are for specifying the member that an initialization value is associated with. It is even possible to initialize members in a different order than they are defined in the `struct`:

        TimeOfDay t = { minute: 42, hour: 7 };

##### Copying and assignment

Structs are value types. As has been described in the Value Types and Reference Types chapter, this means that every `struct` object has its own value. Objects get their own values when constructed, and their values change when they are assigned new values.

        auto yourLunchTime = TimeOfDay(12, 0);
        auto myLunchTime = yourLunchTime;

        // Only my lunch time becomes 12:05:
        myLunchTime.minute += 5;

        // ... your lunch time is still the same:
        assert(yourLunchTime.minute == 0);

During a copy, all of the members of the source object are automatically copied to the corresponding members of the destination object. Similarly, assignment involves assigning each member of the source to the corresponding member of the destination.

Struct members that are of reference types need extra attention.

###### Careful with members that are of reference types!

As you remember, copying or assigning variables of reference types does not change any value, it changes what object is being referenced. As a result, copying or assigning generates one more reference to the right-hand side object. The relevance of this for struct members is that, the members of two separate struct objects would start providing access to the same value.

To see an example of this, let's have a look at a struct where one of the members is a reference type. This struct is used for keeping the student number and the grades of a student:

    struct Student {
        int number;
        int[] grades;
    }

The following code constructs a second `Student` object by copying an existing one:

        // Constructing the first object:
        auto student1 = Student(1, [ 70, 90, 85 ]);

        // Constructing the second student as a copy of the first
        // one and then changing its number:
        auto student2 = student1;
        student2.number = 2;

        // WARNING: The grades are now being shared by the two objects!

        // Changing the grades of the first student ...
        student1.grades[0] += 5;

        // ... affects the second student as well:
        writeln(student2.grades[0]);

When `student2` is constructed, its members get the values of the members of `student1`. Since `int` is a value type, the second object gets its own `number` value.

The two `Student` objects also have individual `grades` members as well. However, since slices are reference types, the actual elements that the two slices share are the same. Consequently, a change made through one of the slices is seen through the other slice.

The output of the code indicates that the grade of the second student has been increased as well:

    75

For that reason, a better approach might be to construct the second object by the copies of the grades of the first one:

        // The second Student is being constructed by the copies
        // of the grades of the first one:
        auto student2 = Student(2, student1.grades.dup);

        // Changing the grades of the first student ...
        student1.grades[0] += 5;

        // ... does not affect the grades of the second student:
        writeln(student2.grades[0]);

Since the grades have been copied by `.dup`, this time the grades of the second student are not affected:

    70

Note: It is possible to have even the reference members copied automatically. We will see how this is done later when covering struct member functions.

##### Struct literals

Similar to being able to use integer literal values like 10 in expressions without needing to define a variable, struct objects can be used as literals as well.

Struct literals are constructed by the object construction syntax.

        TimeOfDay(8, 30) // ← struct literal value

Let's first rewrite the `main()` function above with what we have learned since its last version. The variables are constructed by the construction syntax and are `immutable` this time:

    void main() {
        immutable periodStart = TimeOfDay(8, 30);
        immutable periodDuration = TimeOfDay(1, 15);

        immutable periodEnd = addDuration(periodStart,
                                          periodDuration);

        writefln("Period end: %s:%s",
                  periodEnd.hour, periodEnd.minute);
    }

Note that `periodStart` and `periodDuration` need not be defined as named variables in the code above. Those are in fact temporary variables in this simple program, which are used only for calculating the `periodEnd` variable. They could be passed to `addDuration()` as literal values instead:

    void main() {
        immutable periodEnd = addDuration(TimeOfDay(8, 30),
                                          TimeOfDay(1, 15));

        writefln("Period end: %s:%s",
                  periodEnd.hour, periodEnd.minute);
    }

##### `static` members

Although objects mostly need individual copies of the struct's members, sometimes it makes sense for the objects of a particular struct type to share some variables. This may be necessary to maintain e.g. a general information about that struct type.

As an example, let's imagine a type that assigns a separate identifier for every object that is constructed of that type:

    struct Point {
        // The identifier of each object
        size_t id;

        int line;
        int column;
    }

In order to be able to assign different ids to each object, a separate variable is needed to keep the next available id. It would be incremented every time a new object is created. Assume that `nextId` is to be defined elsewhere and to be available in the following function:

    Point makePoint(int line, int column) {
        size_t id = nextId;
        ++nextId;

        return Point(id, line, column);
    }

A decision must be made regarding where the common `nextId` variable is to be defined. `static` members are useful in such cases.

Such common information is defined as a `static` member of the struct. Contrary to the regular members, there is a single variable of each `static` member for each thread. (Note that most programs consist of a single thread that starts executing the `main()` function.) That single variable is shared by all of the objects of that struct in that thread:

    import std.stdio;

    struct Point {
        // The identifier of each object
        size_t id;

        int line;
        int column;

        // The id of the next object to construct
        static size_t nextId;
    }

    Point makePoint(int line, int column) {
        size_t id = Point.nextId;
        ++Point.nextId;

        return Point(id, line, column);
    }

    void main() {
        auto top = makePoint(7, 0);
        auto middle = makePoint(8, 0);
        auto bottom =  makePoint(9, 0);

        writeln(top.id);
        writeln(middle.id);
        writeln(bottom.id);
    }

As `nextId` is incremented at each object construction, each object gets a unique id:

    0
    1
    2

Since `static` members are owned by the entire type, there need not be an object to access them. As we have seen above, such objects can be accessed by the name of the type, as well as by the name of any object of that struct:

        ++Point.nextId;
        ++bottom.nextId;    // would be the same as above

When a variable is needed not one per thread but one per program, then those variables must be defined as `shared static`. We will see the `shared` keyword in a later chapter.

###### `static this()` for initialization and `static ~this()` for finalization

Instead of explicitly assigning an initial value to `nextId` above, we relied on its default initial value, zero. We could have used any other value:

        static size_t nextId = 1000;

However, such initialization is possible only when the initial value is known at compile time. Further, some special code may have to be executed before a struct can be used in a thread. Such code is written in `static this()` scopes.

For example, the following code reads the initial value from a file if that file exists:

    import std.file;

    struct Point {
    // ...

        enum nextIdFile = "Point_next_id_file";

        static this() {
            if (exists(nextIdFile)) {
                auto file = File(nextIdFile, "r");
                file.readf(" %s", &nextId);
            }
        }
    }

The contents of `static this()` blocks are automatically executed once per thread before the `struct` type is ever used in that thread. Code that should be executed only once for the entire program (e.g. initializing `shared` and `immutable` variables) must be defined in `shared static this()` and `shared static ~this()` blocks, which will be covered in the Data Sharing Concurrency chapter.

Similarly, `static ~this()` is for the final operations of a thread and `shared static ~this()` is for the final operations of the entire program.

The following example complements the previous `static this()` by writing the value of `nextId` to the same file, effectively persisting the object ids over consecutive executions of the program:

    struct Point {
    // ...

        static ~this() {
            auto file = File(nextIdFile, "w");
            file.writeln(nextId);
        }
    }

The program would now initialize `nextId` from where it was left off. For example, the following would be the output of the program's second execution:

    3
    4
    5

##### Problemler

1. Design a struct named `Card` to represent a playing card.

   This struct can have two members for the suit and the value. It may make sense to use an `enum` to represent the suit, or you can simply use the characters ♠, ♡, ♢, and ♣.

   An `int` or a `dchar` value can be used for the card value. If you decide to use an `int`, the values 1, 11, 12, and 13 may represent the cards that do not have numbers (ace, jack, queen, and king).

   There are other design choices to make. For example, the card values can be represented by an `enum` type as well.

   The way objects of this struct will be constructed will depend on the choice of the types of its members. For example, if both members are `dchar`, then `Card` objects can be constructed like this:

           auto card = Card('♣', '2');

2. Define a function named `printCard()`, which takes a `Card` object as a parameter and simply prints it:

       struct Card {
           // ... please define the struct ...
       }

       void printCard(Card card) {
           // ... please define the function body ...
       }

       void main() {
           auto card = Card(/* ... */);
           printCard(card);
       }

   For example, the function can print the 2 of clubs as:

       ♣2

   The implementation of that function may depend on the choice of the types of the members.

3. Define a function named `newDeck()` and have it return 52 cards of a deck as a `Card` slice:

       Card[] newDeck()
       out (result) {
           assert(result.length == 52);

       } do {
           // ... please define the function body ...
       }

   It should be possible to call `newDeck()` as in the following code:

           Card[] deck = newDeck();

           foreach (card; deck) {
               printCard(card);
               write(' ');
           }

           writeln();

   The output should be similar to the following with 52 distinct cards:

       ♠2 ♠3 ♠4 ♠5 ♠6 ♠7 ♠8 ♠9 ♠0 ♠J ♠Q ♠K ♠A ♡2 ♡3 ♡4
       ♡5 ♡6 ♡7 ♡8 ♡9 ♡0 ♡J ♡Q ♡K ♡A ♢2 ♢3 ♢4 ♢5 ♢6 ♢7
       ♢8 ♢9 ♢0 ♢J ♢Q ♢K ♢A ♣2 ♣3 ♣4 ♣5 ♣6 ♣7 ♣8 ♣9 ♣0
       ♣J ♣Q ♣K ♣A

4. Write a function that shuffles the deck. One way is to pick two cards at random by `std.random.uniform`, to swap those two cards, and to repeat this process a sufficient number of times. The function should take the number of repetition as a parameter:

       void shuffle(Card[] deck, int repetition) {
           // ... please define the function body ...
       }

   Here is how it should be used:

           Card[] deck = newDeck();
           shuffle(deck, 1);

           foreach (card; deck) {
               printCard(card);
               write(' ');
           }

           writeln();

   The function should swap cards `repetition` number of times. For example, when called by 1, the output should be similar to the following:

       ♠2 ♠3 ♠4 ♠5 ♠6 ♠7 ♠8 ♠9 ♠0 ♠J ♠Q ♠K ♠A ♡2 ♡3 ♡4
       ♡5 ♡6 ♡7 ♡8 ♣4 ♡0 ♡J ♡Q ♡K ♡A ♢2 ♢3 ♢4 ♢5 ♢6 ♢7
       ♢8 ♢9 ♢0 ♢J ♢Q ♢K ♢A ♣2 ♣3 ♡9 ♣5 ♣6 ♣7 ♣8 ♣9 ♣0
       ♣J ♣Q ♣K ♣A

   A higher value for `repetition` should result in a more shuffled deck:

           shuffled(deck, 100);

   The output:

       ♠4 ♣7 ♢9 ♢6 ♡2 ♠6 ♣6 ♢A ♣5 ♢8 ♢3 ♡Q ♢J ♣K ♣8 ♣4
       ♡J ♣Q ♠Q ♠9 ♢0 ♡A ♠A ♡9 ♠7 ♡3 ♢K ♢2 ♡0 ♠J ♢7 ♡7
       ♠8 ♡4 ♣J ♢4 ♣0 ♡6 ♢5 ♡5 ♡K ♠3 ♢Q ♠2 ♠5 ♣2 ♡8 ♣A
       ♠K ♣9 ♠0 ♣3

   Note: A much better way of shuffling the deck is explained in the solutions.

... çözümler

Copyright © 1999-2026 by the D Language Foundation | Page generated by Ddoc on Wed Aug 5 15:38:51 2026