www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - D doesn't have weak references. So how can I make a associative array

reply Liam McGillivray <yoshi.pit.link.mario gmail.com> writes:
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 08
next sibling parent reply evilrat <evilrat666 gmail.com> writes:
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. ```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
May 09
parent reply Dukc <ajieskola gmail.com> writes:
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
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
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
parent reply Dukc <ajieskola gmail.com> writes:
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
parent evilrat <evilrat666 gmail.com> writes:
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.
May 10
prev sibling parent Steven Schveighoffer <schveiguy gmail.com> writes:
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 09