www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - safe ref counted pointer

reply vit <vit vit.vit> writes:
Hello,
I want implement  safe ref counted pointer (similar to 
std::shared_ptr).
Problem is that access to managed data of ref counted pointer is 
inherently unsafe because ref counted pointer can be released 
(managed data is destroyed) and scope reference/pointer can be 
still on the stack:
Example:

```d

import std.typecons;

scope rc = RefCounted!int(5);

(scope ref int data){
     rc = RefCounted!int(42);

     data = -1; ///dangling reference!
}(rc);

```

It look like i must choose from 2 options:

1) Method which can release managed data or move ownership of 
managed data are  safe but methods accessing managed data must be 
 system.

2) Method which can release managed data or move ownership of 
managed data are   system but methods accessing managed data can 
be  safe

Are this 2 options valid? (in -dip1000)

I implement ref counted pointer which use both options.
SharedPtr with otpion 1. and ScopedSharedPtr with option 2.
SharedPtr is convertable to ScopedSharedPtr by copy or move.
ScopedSharedPtr is convertable to SharedPtr by copy.
(Code ignore qualifiers, work only with integers, is thread local 
only and has not custom allocators...)

Is use of  trusted in this code valid? (git link: 
https://gist.github.com/run-dlang/1d18c71d0ad89409684969e7c155137e)

```d
import core.lifetime : forward, move, emplace;
import std.experimental.allocator.mallocator;
import std.traits : isIntegral, isMutable;
import std.stdio : writeln;


void main(){
     scope SharedPtr!long top = SharedPtr!long.make(-1);

     writeln("shared ptr:");
     //shared ptr:
     () system{
         scope SharedPtr!long x = SharedPtr!long.make(42);
         (scope ref long data) safe{
             x.release();					///release is  safe
             x = SharedPtr!long.make(123);	///opAssign is  safe
             top = move(x);					///move is  safe
             //data = 314;					///dangling pointer

         }(x.get);					///get is  system
     }();

     writeln("scoped shared ptr:");
     ///scoped shared ptr:
     () safe{
         scope ScopedSharedPtr!long x = SharedPtr!long.make(654);
         (scope ref long data) safe{
             //x.release(); 					///release is  system
             //x = SharedPtr!long.make(123);	///opAssign is  system
             //top = move(x);				///opPostMove is  system

             top = x;						//copy is ok
             data = -data;					//cannot be dangling 
pointer/reference

         }(x.get);							//get is  safe

     }();

}

alias ScopedSharedPtr(T) = SharedPtr!(T, true);

struct SharedPtr(T, bool scoped = false)
if(isIntegral!T && isMutable!T){

     //copy ctor:
     this(scope ref typeof(this) rhs) trusted{
         if(rhs.impl){
             this.impl = rhs.impl;
             this.impl.counter += 1;
         }
     }

     //forward ctor impl
     this(bool s)(scope auto ref SharedPtr!(T, s) rhs, 
typeof(null)) trusted{
         if(rhs.impl){
             this.impl = rhs.impl;

             static if(__traits(isRef, rhs))
                 this.impl.counter += 1;
             else
                 rhs.impl = null;
         }
     }

     //forward ctor (constraint ignore move ctor)
     this(bool s)(scope auto ref SharedPtr!(T, s) rhs) trusted
     if(__traits(isRef, rhs) || s != scoped){
         if(rhs.impl){
             this.impl = rhs.impl;

             static if(__traits(isRef, rhs))
                 this.impl.counter += 1;
             else
                 rhs.impl = null;
         }
     }

     //forward assignment
     void opAssign(bool s)(scope auto ref SharedPtr!(T, s) 
rhs)scope{

         if((() trusted => cast(void*)&this is cast(void*)&rhs )())
             return;

         this.release();

         if(rhs.impl){
             () trusted{
                 this.impl = rhs.impl;
             }();

             static if(__traits(isRef, rhs))
                 this.impl.counter += 1;
             else
                 rhs.impl = null;
         }
     }


     static auto make(Args...)(auto ref Args args) safe{
         return typeof(this)(Impl.construct(forward!args));
     }



     ///ScopedSharedPtr:
     static if(scoped){

         // system move:
         void opPostMove(const ref typeof(this)) system{
         }

         // system release
         void release()scope  system{
             this.release_impl();
         }

         // safe get:
          property ref inout(T) get()inout return  safe pure 
nothrow  nogc{
             assert(impl !is null);
             return impl.elm;
         }
     }
     ///SharedPtr:
     else{
         // safe release
         void release()scope  safe{
             this.release_impl();
         }

         // system get:
          property ref inout(T) get()inout return  system pure 
nothrow  nogc{
             assert(impl !is null);
             return impl.elm;
         }
     }


     ~this() safe{
         this.release_impl();
     }

     private void release_impl()scope  safe{
         if(impl){
             impl.counter -= 1;
             if(impl.counter == 0){
                 impl.destruct();
                 impl = null;
             }
         }
     }

     private alias Impl = ControlBlock!T;

     private Impl* impl;

     private this(Impl* impl) safe{
         this.impl = impl;
     }
}

private struct ControlBlock(T){
     T elm;
     int counter;

     static ControlBlock* construct(Args...)(auto ref Args 
args) safe{
         writeln("alloc: ", args);
         void[] data = 
Mallocator.instance.allocate(ControlBlock.sizeof);
         ControlBlock* impl = (() trusted => 
cast(ControlBlock*)data.ptr )();
         if(impl){
             emplace(&impl.elm, forward!args);
             impl.counter = 1;
         }
         return impl;
     }

     void destruct() trusted{
         writeln("delloc: ", elm);
         destroy(elm);
         const result = 
Mallocator.instance.deallocate((cast(void*)&this)[0 .. 
ControlBlock.sizeof]);
         assert(result);

     }
}
```
Jan 12 2022
parent Stanislav Blinov <stanislav.blinov gmail.com> writes:
On Wednesday, 12 January 2022 at 21:17:13 UTC, vit wrote:
 Hello,
 I want implement  safe ref counted pointer (similar to 
 std::shared_ptr).
 Problem is that access to managed data of ref counted pointer 
 is inherently unsafe because ref counted pointer can be 
 released (managed data is destroyed) and scope 
 reference/pointer can be still on the stack:
 Example:

 ```d

 import std.typecons;

 scope rc = RefCounted!int(5);

 (scope ref int data){
     rc = RefCounted!int(42);

     data = -1; ///dangling reference!
 }(rc);

 ```

 It look like i must choose from 2 options:

 1) Method which can release managed data or move ownership of 
 managed data are  safe but methods accessing managed data must 
 be  system.

 2) Method which can release managed data or move ownership of 
 managed data are   system but methods accessing managed data 
 can be  safe
3) Leave `get()` ` system` and simply make a ` safe` caller: ```d struct SharedPtr(T) { // ... auto apply(Dg)(scope Dg dg) if (is(typeof(dg(typeof(this).init.get)))) { auto tmp = this; // borrow another reference return dg(ref () trusted { return get(); } ()); } // ... } void main() safe { scope rc = SharedPtr!int(5); rc.apply((scope ref data) { rc = SharedPtr!int(42); data = -1; // ok }); } ```
Jan 12 2022