Discuss: Classes are well supported in D, with Design by Contracts. Shouldn't we take advantage of that?
Jonathan M Davis
newsgroup.d at jmdavisprog.com
Sun Sep 14 00:55:54 UTC 2025
On Friday, September 12, 2025 6:43:38 PM Mountain Daylight Time Brother Bill via Digitalmars-d-learn wrote:
> I'm not clear about why 'class'es are on the 'avoid' list.
It's more that there's rarely any reason to use classes in D for the average
program. If you need inheritance and polymorphism, you use classes. You
might also use classes if you want to force the type to be a reference type.
But most types don't need inheritance or polymorphism, and templates make it
so that code can be shared without needing a common base type. And if you
use a struct, you have the choice of whether it's going to live on the stack
or on the heap, whereas classes normally have to live on the heap.
The result of all of this is that structs are the default choice in D rather
than classes. There's just no reason to use a class if you don't actually
need what makes classes different from structs. So, if you don't need a
class, you don't use a class, whereas if you do need a class, then you use a
class. Programmers who use classes in D when they didn't actually need to
are almost always programmers who are new to D.
So, if you're seeing advice about avoiding classes in D, it's typically
going to be because some of the folks coming to D assume that they should
use object-oriented design with inheritance all over the place, because
that's what they're used to doing in languages which are less flexible (or
they use classes simply because they're used to using the class keyword, so
they grab that first), and there's no need to take that approach with D by
default. So, D programmers are typically going to give the advice that you
shouldn't use classes unless that's what you actually need. But that doesn't
mean that classes shouldn't be used. It just means that you should use
structs rather than classes unless what you're doing is actually better
served by using classes. There's no reason to avoid classes when they're
actually the best tool for the job. It's just that they're usually not the
best tool for the job in D, whereas folks coming from other languages are
used to using classes, so that's often what they first do in D.
> D has excellent support for Single inheritance, Interfaces,
> Design by Contract (DbC), GC, etc.
> I'm aware that there is a small run time cost for selecting the
> right virtual method.
>
> To reduce this cost, one must final-ize methods that don't need
> to be overridden.
>
> Using classes is a major reason that I chose D.
> Outside of Eiffel language, D comes closest to supporting DbC.
> Even .NET has abandoned supporting DbC.
Well, D's DbC is kind of broken insofar as it's compiled into the callee
code and not the caller code. So, if a library has contracts, whether
they're compiled in or not is based on whether that library was compiled
with them enabled or not, whereas what's really needed is for them to
compiled in based on whether the caller is compiled with contracts enabled
or not, because at least with in contracts, the whole point is to test that
the caller code is doing the correct thing, not that the library is doing
the correct thing.
For most functions, using an in contract adds nothing over simply putting
the assertion at the start of the function (the one exception being with
virtual functions, since the compiler will do boolean logic based on which
contracts succeed or fail in the inheritance tree for that function). So, in
the vast majority of cases, the assertions might as well just go at the
start of the function, because using an in contract instead adds no value.
Of course, if they were compiled in at the call site, then they _would_
actually have some value, because they'd provide actual contracts which the
caller could use to test their code. However, in practice (aside from the
inheritance case), while they're supposed to be testing the caller's code,
they're being treated just like any assertion in the callee code, making it
so that you might as well just use assertions in the function body, because
it's basically the same thing. So, even if in contracts are theoretically of
value, in practice, they really aren't, though changes to how D compiles
contracts could change that.
Out contracts are also generally useless (and really would be no matter how
they were compiled in), because in the vast majority of cases, to test
whether the output is correct or not, you need to know both what the input
was and what the correct result for that input is, and that job is much
better done with unit tests. There are rare exceptions where an out contract
might still make sense, because there's some condition which you want to
test for which isn't dependent on the input, and with virtual functions, you
do get the added boolean logic with the inheritance tree, but realistically,
out contracts could be removed from the language and have almost no impact
on existing code. The cases where they could even theoretically be useful
are just too rare.
Invariants on the other hand can add real value, because they add checks for
all public member function calls, and inserting those checks yourself for
each function would be far more verbose. But even then, you do have to be
careful with them, because they tend to fall apart with any types which give
any access to their member variables, since that allows code to bypass the
invariant and potentially invalidate it. So, they're definitely the most
useful part of D's DbC implementation, but even then, I don't think that
they get used very often.
In practice, I've occasionally seen folks use in contracts (though they all
could have been normal assertions, since I don't think that I've ever seen
contracts used with classes in the wild), and I've occasionally seen
invariants used, but most D programmers don't use D's DbC features. Of
course, some do, and if you want to use them, they're there. But in
contracts in particular are flawed with how they currently work in D. So, I
wouldn't suggest using D's DbC outside of cases where invariants make sense,
but do whatever works for you.
- Jonathan M Davis
More information about the Digitalmars-d-learn
mailing list