Why are structs and classes so different?

Mike Parker aldacron at gmail.com
Sun May 15 16:08:01 UTC 2022


On Sunday, 15 May 2022 at 15:26:40 UTC, Kevin Bailey wrote:

> I'm trying to understand why it is this way. I assume that 
> there's some benefit for designing it this way. I'm hoping that 
> it's not simply accidental, historical or easier for the 
> compiler writer.

There's a problem that arises with pass-by-value subclasses 
called "object slicing". Effectively, it's possible to "slice 
off" members of superclasses. It's one of many pitfalls to be 
avoided in C++. There you typically find the convention of 
structs being used as POD (Plain Old Data) types (i.e., no 
inheritance) and classes when you want inheritance, in which case 
they are passed around to functions as references.

For reference:
https://stackoverflow.com/questions/274626/what-is-object-slicing

D basically bakes the C++ convention into the language, with a 
class system inspired by Java.

>
> One problem that this causes is that I have to remember 
> different rules when using them. This creates the additional 
> load of learning and remembering which types are which from 
> someone else's library.

Of all the complexity we need to remember as programmers, this is 
fairly low on the totem pole. Simple rule: if you need (or want) 
inheritance, use classes. If not, use structs.


>
> A bigger problem is that, if I have a struct that I suddenly 
> want to inherit from, I have to change all my code.

You should generally know up front if you need inheritance or 
not. In cases where you change your mind, you'll likely find that 
you have very little code to change. Variable declarations, sure. 
And if you were passing struct instances to functions, you'd want 
to change the function signatures, but that should be the lion's 
share of what you'd need to change. After all, D doesn't use the 
`->` syntax for struct pointers, so any members you access would 
be via the dot operator.

> In addition to that work, in both of these cases, one could 
> easily do it wrong:
>
> // Fine with a struct, fatal with a class.
> Foo foo;
>
> At least in C++, the compiler would complain. In D, not even a 
> warning.

You generally find out about that pretty quickly in development, 
though. That's a good reason to get into the habit of 
implementing and running unit tests, so if you do make changes 
and overlook something like this, then your tests will catch it 
if normal operation of the program doesn't.

>
> Why is it this way? What is the harm of putting a class object 
> on the stack?

I've answered the "why" above. As to the the second question, 
there's no harm in putting a class on the stack:

```d
import std.stdio;

class Clazz {
     ~this() { writeln("Bu-bye"); }
}

void clazzOnStack() {
     writeln("Entered");
     scope c = new Clazz;
     writeln("Leaving");
}

void main()
{
     clazzOnStack();
     writeln("Back in main");
}
```

You'll find here that the destructor of `c` in `clazzOnStack` is 
called when the function exits, just as if it were a struct. 
`scope` in a class variable declaration will cause it to the 
class to be allocated on the stack.

Note, though, that `c` *still* a reference to the instance. You 
aren't manipulating the class instance directly. If you were to 
pass `c` to a function `doSomething` that accepts a `Clazz` 
handle, it makes no difference that the instance is allocated on 
the stack. `doSomething` would neither know nor care. `c` is a 
handle, so you aren't passing the instance directly and it 
doesn't matter where it's allocated.

There's more to the story than just reference type vs. value 
type. Structs have deterministic destruction, classes by default 
do not (`scope` can give it to you as demonstrated above). [See 
my blog post on the 
topic](https://dlang.org/blog/2021/03/04/symphony-of-destruction-structs-classes-and-the-gc-part-one/) for some info. (And I'm reminded I need to write the next article in that series; time goes by too fast).

Everyone has their own criteria for when to choose class and when 
to choose struct. For me, I default to struct. I consider 
beforehand if I need inheritance, and if yes, then I ask myself 
if I can get by without deterministic destruction. There are ways 
to simulate inheritance with structs, and ways to have more 
control over destruction with classes, so there are options 
either way.


More information about the Digitalmars-d-learn mailing list