The D standard library is built on GC, is that a negative or positive?

cc cc at nevernet.com
Wed Dec 14 08:01:47 UTC 2022


On Tuesday, 13 December 2022 at 07:11:34 UTC, thebluepandabear 
wrote:
> Does this claim have merit? I am not far enough into learning 
> D, so I haven't touched GC stuff yet, but I am curious what the 
> D community has to say about this issue.

I disagree with the majority opinion on this subject.  I find D's 
GC to often be heavily oversold, when it is particularly not 
applicable to many portions of my use case (game development).  
It certainly solves certain problems, but it introduces new ones. 
  The emphasis behind the GC mentality seems to be that most(all!) 
people will never encounter those for their purposes and so they 
should literally just not think about it and trust the GC, until 
you suddenly can't anymore and the whole thing breaks apart.  
Alternative strategies do exist obviously, but they're often 
shoved into the backroom, with the salespeople only leading the 
customers to them after much grumbling and fumbling with their 
keys.

How do you instantiate a class object in D using the GC?
```d
new Foo;
```
How do you instantiate one using malloc?  Something like:
```d
import core.memory;
import core.stdc.stdlib : malloc, free;
import core.lifetime : emplace;
T NEW(T, Args...)(auto ref Args args) /*@nogc (nope!)*/ if (is(T 
== class)) {
	enum size = __traits(classInstanceSize, T);
	void* mem = malloc(size);
	scope(failure) free(mem);
	//throw OOMError.get("Out of Memory in NEW!"~T.stringof); // 
wanna GC-allocate here? use a predefined object? or just ignore?
	return mem !is null ? emplace!T(mem[0..size], args) : null;
}
// and don't forget
void FREE(T)(ref T obj) /*@nogc*/ if (is(T == class)) {
	if (obj is null) return;
	auto mem = cast(void*) obj;
	//debug if (!GC.inFinalizer && GC.addrOf(mem)) return;
	scope(exit) free(mem);
	destroy(obj);
	obj = null;
}
```

To people who are experienced with D, that's par for the course.  
And people who have already done a good deal of thinking about 
memory management in performance-intensive scenarios will 
understand the need to know their language's alternatives to 
begin with.  But showing that to people coming to D as the 
alternative to what you're supposed to think is the *right* way 
everyone should use is just not attractive.  It's a contradiction 
in one of D's core philosophies, IMO: "Solve basic problems and 
prevent easy bugs that most people walk into without thinking", 
which sounds like an admirable goal aimed at drawing in and 
protecting new users.  Except then they're given a tool that will 
just create problems if used in the intended way (not thinking 
about it) if they get into certain domains of development.  The 
explanation of "Well, obviously you need to think about it if 
you're going to be doing THAT!" just doesn't mesh with the way 
it's initially sold.

Pipe dream: Why not `new malloc Foo;`? (or "deterministic" or 
something. and then, `new rc Foo;`!)  What if, to prevent 
accidental intermingling, it were a storage class? `malloc Foo 
mfoo = new Foo; // Error!`.  Just thinking out loud.  Part of 
this can already be done by wrapping everything in structs and 
templates.  But just more noise!


That said, regarding your specific question, there are numerous 
parts of the D standard library you can safely use without 
allocating with the GC (and I do, and still love it) and 
non-allocating alternatives are often added (e.g. `.join` vs 
`.joiner`).   Though the problem exists many components can't be 
*explicitly* `@nogc` (a caveat I find it just not worth it to 
worry about anymore- it takes up too much time and effort that 
could be better spent on the code itself than on solving a 
trillion compiler errors trying to satisfy every possible aspect 
and edge case of @nogc-dom).

There is a lot of code you can write in D that, without going 
over the stdlib with a fine-toothed comb, you can be reasonably 
sure *will probably never* GC-allocate, even if it's not 
explicitly @nogc, if that's an acceptable tradeoff for the code 
safety requirements in your use case.  std.container.array is 
good, as previously mentioned.  You can build on this and make 
malloc/ref-counted variations of hashmaps/associative arrays too 
(if you want to spend the effort on it).  I believe there are 
some third-party libraries up on dub for that already.  The 
operator overloading and syntactic sugar is good enough that 
everything can look "just like" native D runtime/GC constructs if 
you want, except for some declarations (but all that is not 
exactly "out of the box", if we're still thinking of the 
prospective new user context here).

tl;dr: I don't *hate* the GC.  It's great for one&dones.  I just 
wish it wasn't so heavily lauded as The Truth & The Way.


More information about the Digitalmars-d mailing list