pure
    Preparing search index...
    • Create a constructor function that serves as the type for an externally ref-counted object type. Erc instances can then be created for manually managing memory for external objects, typically through FFI.

      Since JS is garbage collected and has no way to enforce certain memory management, the programmer must ensure that Erc instances are handled correctly!!!

      import { makeErcType, type Erc } from "@pistonite/pure/memory";

      // 2 functions are required to create an Erc type:
      // free: free the underlying value (essentially decrementing the ref count)
      // addRef: increment the ref count and return the new reference

      // here, assume `number` is the JS type used to represent the external object
      // for example, this can be a pointer to a C++ object
      declare function freeFoo(obj: number) => void;
      declare function addRefFoo(obj: number) => number;

      // assume another function to create (allocate) the object
      declare function createFoo(): number;

      // The recommended way is to create a unique symbol for tracking
      // the external type, you can also use a string literal
      const Foo = Symbol("Foo");
      type Foo = typeof Foo;

      // now, we can create the Erc type
      const makeFooErc = makeErcType({
      marker: Foo,
      free: freeFoo,
      addRef: addRefFoo,
      });

      // and create Erc instances
      const myFoo: Erc<Foo> = makeFooErc(createFoo());

      Each Erc instance is a strong reference, corresponding to some ref-counted object externally. Therefore, owner of the Erc instance should not expose the Erc instance to others (for example, returning it from a function), since it will lead to memory leak or double free.

      // create a foo instance externally, and wrap it with Erc
      const myFoo = makeFooErc(createFoo());

      // if ownership of myFoo should be returned to external, use `take()`
      // this will make myFoo empty, and doSomethingWithFooExternally should free it
      doSomethingWithFooExternally(myFoo.take());

      // you can also free it directly
      foo.free();

      Calling getWeak on an Erc will return a weak reference that has the same inner value. The weak reference is safe to be passed around and copied.

      const myFooWeak = myFoo.getWeak();
      

      The weak references are tracked by the Erc instance. In the example above, when myFoo is freed, all weak references created by getWeak will be invalidated. If some other code kept the inner value of the weak reference, it will become a dangling pointer.

      To avoid this, getStrong can be used to create a strong reference if the weak reference is still valid, to ensure that the underlying object is never freed while still in use

      const myFooWeak = myFoo.getWeak();

      // assume we have some async code that needs to use myFoo
      declare async function doSomethingWithFoo(foo: FooErcRef): Promise<void>;

      // Below is BAD!
      await doSomethingWithFoo(myFooWeak);
      // Reason: doSomethingWithFoo is async, so it's possible that
      // myFooWeak is invalidated when it's still needed. If the implementation
      // does not check for that, it could be deferencing a dangling pointer
      // (of course, it could actually be fine depending on the implementation of doSomethingWithFoo)

      // Recommendation is to use strong reference for async operations
      const myFooStrong = myFooWeak.getStrong();
      await doSomethingWithFoo(myFooStrong.getWeak()); // will never be freed while awaiting
      // now we free
      myFooStrong.free();

      Each Erc instance should only ever have one external reference. So you should not assign to an Erc variable directly:

      // DO NOT DO THIS
      let myFoo = makeFooErc(createFoo());
      myFoo = makeFooErc(createFoo()); // previous Erc is overriden without proper clean up

      If you want to attach a new value to an existing Erc, use the assign method:

      const myFoo = makeFooErc(createFoo());
      myFoo.assign(createFoo()); // previous Erc is freed, and the new one is assigned
      myFoo.free(); // new one is freed

      The example above does not cause leaks, since the previous Erc is freed

      However, if you call assign with the value of another Erc, it will cause memory issues:

      // DO NOT DO THIS
      const myFoo1 = makeFooErc(createFoo());
      const myFoo2 = makeFooErc(createFoo());
      myFoo1.assign(myFoo2.value); // myFoo1 is freed, and myFoo2 is assigned
      // BAD: now both myFoo1 and myFoo2 references the same object, but the ref count is 1
      myFoo1.free(); // no issue here, object is freed, but myFoo2 now holds a dangling pointer
      myFoo2.free(); // double free!

      // The correct way to do this is to use `take`:
      const myFoo1 = makeFooErc(createFoo());
      const myFoo2 = makeFooErc(createFoo());
      myFoo1.assign(myFoo2.take()); // myFoo1 is freed, and myFoo2 is assigned, myFoo2 is empty
      myFoo1.free(); // no issue here, object is freed
      // myFoo2 is empty, so calling free() has no effect

      Assign also works if both Erc are 2 references of the same object:

      const myFoo1 = makeFooErc(createFoo()); // ref count is 1
      const myFoo2 = myFoo1.getStrong(); // ref count is 2
      myFoo1.assign(myFoo2.take()); // frees old value, ref count is 1

      // This also works:
      const myFoo1 = makeFooErc(createFoo()); // ref count is 1
      myFoo1.assign(myFoo1.take()); // take() makes myFoo1 empty, so assign() doesn't free it

      // DO NOT DO THIS:
      myFoo1.assign(myFoo1.value); // this will free the value since ref count is 0, and result in a dangling pointer

      Type Parameters

      • TName
      • TRepr

      Parameters

      Returns (value: undefined | TRepr) => Erc<TName, TRepr>