Thread overview
D doesn't have weak references. So how can I make a associative array of objects without preventing their destruction?
May 09
evilrat
May 10
Dukc
May 10
Dukc
May 10
evilrat
May 09

A "weak reference" (in the sense that I'm referring to) is a feature in some programming languages for a reference to an object that doesn't prevent the GC from destroying that object.

My current understanding is that D doesn't have weak references, though I've found some posts in this forum from many years back that mention something called "weakref". So is weakref a real thing, or just a concept that never got implemented?

The functionality that I'm going to describe would be easy with weak references, but I don't know how I would implement it without it. If there is a way to implement it without it, I would like to know how. I am going to describe my specific example, but it may apply to any class that's initialized using contents of a file without any of that data being modified after.

In my particular case, the class I've created is a wrapper for the Texture2D struct in Raylib. This class holds an image that was loaded from a file.

Sprite[string] spritesByPath;

Sprite getSprite(string path) {
    path = path.asAbsolutePath;

    if (path !in spritesByPath) {
        spritesByPath[path] = new Sprite(path);
    }

    return spritesByPath[path];
}

class Sprite
{
    Texture2D texture;
    alias this = texture;
    string path;

    this(string path) {
        texture = LoadTexture(path.toStringz);
        this.path = path;
    }

    ~this() {
        if (IsWindowReady) UnloadTexture(texture);
        if (path in spritesByPath) spritesByName.remove(path);
    }
}

Alternatively, spritesByPath and getSprite may be static members of Sprite.

If D had weak references, than spritesByPath would be made of weak references so that they don't prevent the destruction of Sprite objects, which should be destroyed whenever they don't have any references elsewhere.

I've considered making Sprite reference-counted, but I couldn't manage to figure out how to do it properly. I tried doing SafeRefCounted!Sprite but the compiler said it doesn't work on Object types. I then tried making my own struct for reference counting that would be placed in place of a direct reference to the Sprite object, but there was some bug in which sometimes it didn't increment the reference count, so it didn't work.

What's a good way I can achieve what I'm trying to do, using either reference counting or a garbage-collected object?

May 09

On Thursday, 9 May 2024 at 00:39:49 UTC, Liam McGillivray wrote:

>

What's a good way I can achieve what I'm trying to do, using either reference counting or a garbage-collected object?

There is libraries like automem[1] that implements refcounting and more.
Without showing your code for ref counted struct we can't help you.

As for weak references, maybe you could "trick" the GC by using the fact that simple types are not scanned, i.e. do something like this but I have no idea if this is going to work at all, alternatively you can also try using ubyte[size_t.sizeof].

Keep in mind that classes is already references so you don't need that extra pointer for classes, can be versioned with template specialization.

struct WeakRef(T) {
    private size_t _handle; // same size as a pointer

    this(T* ptr) {
        _handle = cast(size_t) ptr;
    }

    T* getRef() {
        return cast(T*) _handle;
    }

    // do the rest ...
}

[1] https://code.dlang.org/packages/automem

May 09

On Thursday, 9 May 2024 at 00:39:49 UTC, Liam McGillivray wrote:

>

A "weak reference" (in the sense that I'm referring to) is a feature in some programming languages for a reference to an object that doesn't prevent the GC from destroying that object.

My current understanding is that D doesn't have weak references, though I've found some posts in this forum from many years back that mention something called "weakref". So is weakref a real thing, or just a concept that never got implemented?

No, D does not have any built-in support for weak references.

>

What's a good way I can achieve what I'm trying to do, using either reference counting or a garbage-collected object?

In order to have weak references, they have to be invalidated when the item itself is destroyed. I think the most logical mechanism is to have a registry of weak reference ids, and you point at that. Then the item itself, when destroyed, goes and updates the weak reference registry with a null pointer so the weak references when looking up the value get a null pointer.

The registry needs to outlive all other weak references, and the things that its pointing to. This means probably either reference counting or some global structure that is never deleted. The key thing is that the registry cannot be collected before the things that use it.

-Steve

May 10
evilrat kirjoitti 9.5.2024 klo 18.19:
> ```d
> struct WeakRef(T) {
>      private size_t _handle; // same size as a pointer
> 
>      this(T* ptr) {
>          _handle = cast(size_t) ptr;
>      }
> 
>      T* getRef() {
>          return cast(T*) _handle;
>      }
> 
>      // do the rest ...
> }
> ```
> 
> [1] https://code.dlang.org/packages/automem

There is a hidden danger with using this struct. Since `getRef` is a template, it will be inferred as `pure`. Now, consider a function using it:

```D
auto derefer(WeakrefT)(WeakrefT wr) => *wr.getRef;
```

This also gets inferred as `pure` - meaning that if you use it twice for the same `WeakRef`, the compiler may reuse the result of the first dereference for the second call, without checking whether the referred value has changed!

You probably should add some never-executed dummy operation to `getRef` that prevents it from becoming `pure` if you go with this design.
May 10

On Friday, 10 May 2024 at 11:05:28 UTC, Dukc wrote:

>

This also gets inferred as pure - meaning that if you use it twice for the same WeakRef, the compiler may reuse the result of the first dereference for the second call, without checking whether the referred value has changed!

This would be weak pure since the reference is mutable. This cannot be memoized.

-Steve

May 10
Steven Schveighoffer kirjoitti 10.5.2024 klo 16.01:
> On Friday, 10 May 2024 at 11:05:28 UTC, Dukc wrote:
>> This also gets inferred as `pure` - meaning that if you use it twice for the same `WeakRef`, the compiler may reuse the result of the first dereference for the second call, without checking whether the referred value has changed!
> 
> This would be weak pure since the reference is mutable. This cannot be memoized.

The difference is the type. With a pointer parameter (both a bare one and one in a struct), the compiler can cache the result only when the pointed data is similar. However, here we have an integer parameter. It can be cached if it itself is similar to the one in the other function call. The compiler doesn't have to know, nor can know, when a `size_t` is a pointer in disguise.
May 10
On Friday, 10 May 2024 at 13:27:40 UTC, Dukc wrote:
> Steven Schveighoffer kirjoitti 10.5.2024 klo 16.01:
>> On Friday, 10 May 2024 at 11:05:28 UTC, Dukc wrote:
>>> This also gets inferred as `pure` - meaning that if you use it twice for the same `WeakRef`, the compiler may reuse the result of the first dereference for the second call, without checking whether the referred value has changed!
>> 
>> This would be weak pure since the reference is mutable. This cannot be memoized.
>
> The difference is the type. With a pointer parameter (both a bare one and one in a struct), the compiler can cache the result only when the pointed data is similar. However, here we have an integer parameter. It can be cached if it itself is similar to the one in the other function call. The compiler doesn't have to know, nor can know, when a `size_t` is a pointer in disguise.

This why I would just use ref counting if I were the topic author, trying to be smart will often comes back when one doesn't expect.

And as stated by previous author if the goal is to have self-clearable weak reference it will need some infrastructure anyway, so tbh this will greatly outweight any possible benefits of having weak refs.