Difference between revisions of "DIP74"

From D Wiki
Jump to: navigation, search
(AddRef -> opAddRef, Release -> opRelease)
Line 35: Line 35:
 
<syntaxhighlight lang=D>
 
<syntaxhighlight lang=D>
 
class Widget {
 
class Widget {
     T1 AddRef();
+
     T1 opAddRef();
     T2 Release();
+
     T2 opRelease();
 
     ...
 
     ...
 
}
 
}
Line 43: Line 43:
 
<tt>T1</tt> and <tt>T2</tt> may be any types (usually <tt>void</tt> or an integral type). The methods may or may not be <tt>final</tt> or inherited. Any attributes are allowed on these methods. They must be public. UFCS-expanded calls are not acceptable. If these two methods exist and are accessible, the compiler categorizes this <tt>class</tt> or <tt>interface</tt> type as a ''reference counted reference'' and treats it as follows:
 
<tt>T1</tt> and <tt>T2</tt> may be any types (usually <tt>void</tt> or an integral type). The methods may or may not be <tt>final</tt> or inherited. Any attributes are allowed on these methods. They must be public. UFCS-expanded calls are not acceptable. If these two methods exist and are accessible, the compiler categorizes this <tt>class</tt> or <tt>interface</tt> type as a ''reference counted reference'' and treats it as follows:
  
* Whenever a new reference to an object is created (e.g. <tt>auto a = b;</tt>, compiler inserts a call to <tt>AddRef</tt> in the generated code. Call is inserted only if the reference is not <tt>null</tt>. The lowering of <tt>auto a = b;</tt> is conceptually <tt>if (b) b.AddRef(); auto a = b;</tt>
+
* Whenever a new reference to an object is created (e.g. <tt>auto a = b;</tt>, compiler inserts a call to <tt>opAddRef</tt> in the generated code. Call is inserted only if the reference is not <tt>null</tt>. The lowering of <tt>auto a = b;</tt> is conceptually <tt>if (b) b.opAddRef(); auto a = b;</tt>
  
* There is no call inserted for the first reference created via a constructor (i.e. it is assumed the constructor already puts the object in the appropriate state). For example the lowering of <tt>auto a = new Widget;</tt> does not insert a call to <tt>AddRef</tt>.
+
* There is no call inserted for the first reference created via a constructor (i.e. it is assumed the constructor already puts the object in the appropriate state). For example the lowering of <tt>auto a = new Widget;</tt> does not insert a call to <tt>opAddRef</tt>.
  
* Whenever a reference to an object is assigned (e.g. <tt>a = b</tt>), first <tt>b.AddRef()</tt> is called and then <tt>a.Release()</tt> is called, followed by the reference assignment itself. Calls are only made if the respective objects are not <tt>null</tt>. So the lowering of e.g. <tt>a = b;</tt> is <tt>if (b) b.AddRef(); if (a) a.Release(); a = b;</tt>
+
* Whenever a reference to an object is assigned (e.g. <tt>a = b</tt>), first <tt>b.opAddRef()</tt> is called and then <tt>a.opRelease()</tt> is called, followed by the reference assignment itself. Calls are only made if the respective objects are not <tt>null</tt>. So the lowering of e.g. <tt>a = b;</tt> is <tt>if (b) b.opAddRef(); if (a) a.opRelease(); a = b;</tt>
  
* Whenever a reference to an object goes out of scope, the compiler inserts an implicit call to <tt>Release</tt>. Call is inserted only if the reference is not <tt>null</tt>.
+
* Whenever a reference to an object goes out of scope, the compiler inserts an implicit call to <tt>opRelease</tt>. Call is inserted only if the reference is not <tt>null</tt>.
  
* The pass-by-value protocol for reference counted references is as follows: the caller calls <tt>AddRef</tt> and the callee calls </tt>Release</tt>. These calls are sequenced and handled the same as copy constructor calls and destructor calls, respectively, for <tt>struct</tt> objects. Example:
+
* The pass-by-value protocol for reference counted references is as follows: the caller calls <tt>opAddRef</tt> and the callee calls </tt>opRelease</tt>. These calls are sequenced and handled the same as copy constructor calls and destructor calls, respectively, for <tt>struct</tt> objects. Example:
  
 
<syntaxhighlight lang=D>
 
<syntaxhighlight lang=D>
Line 66: Line 66:
 
# All parameters are <tt>memcpy</tt>'d
 
# All parameters are <tt>memcpy</tt>'d
 
# Postblit call for <tt>x</tt>
 
# Postblit call for <tt>x</tt>
# <tt>y.AddRef()</tt>
+
# <tt>y.opAddRef()</tt>
 
# Postblit call for <tt>z</tt>
 
# Postblit call for <tt>z</tt>
 
# Function is entered
 
# Function is entered
 
# Destructor call for <tt>z</tt>
 
# Destructor call for <tt>z</tt>
# <tt>y.Release()</tt>
+
# <tt>y.opRelease()</tt>
 
# Destructor call for <tt>x</tt>
 
# Destructor call for <tt>x</tt>
 
# Function returns
 
# Function returns
  
* Functions that return a reference counted reference call <tt>AddRef</tt> against the returned reference, EXCEPT if the returned reference is an rvalue or a local variable. Functions that return a local variable do not call <tt>Release</tt> against it.
+
* Functions that return a reference counted reference call <tt>opAddRef</tt> against the returned reference, EXCEPT if the returned reference is an rvalue or a local variable. Functions that return a local variable do not call <tt>opRelease</tt> against it.
  
* The compiler considers that <tt>Release</tt> is the inverse of <tt>AddRef</tt>, and therefore is at liberty to elide pairs of calls to <tt>AddRef</tt>/<tt>Release</tt>. Example:
+
* The compiler considers that <tt>opRelease</tt> is the inverse of <tt>opAddRef</tt>, and therefore is at liberty to elide pairs of calls to <tt>opAddRef</tt>/<tt>opRelease</tt>. Example:
  
 
<syntaxhighlight lang=D>
 
<syntaxhighlight lang=D>
Line 86: Line 86:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Applying the rules defined above would have <tt>fun</tt>'s lowering insert one call to <tt>AddRef</tt> (for creating <tt>b</tt>) and one call to <tt>Release</tt> (when <tt>a</tt> goes out of scope). However, these calls may be elided.
+
Applying the rules defined above would have <tt>fun</tt>'s lowering insert one call to <tt>opAddRef</tt> (for creating <tt>b</tt>) and one call to <tt>opRelease</tt> (when <tt>a</tt> goes out of scope). However, these calls may be elided.
  
 
* Implicit conversion to supertypes (<tt>class</tt> or <tt>interface</tt>) is allowed ONLY if the supertype is also a reference counted type. It follows that reference counted types cannot be converted to <tt>Object</tt> (unless <tt>Object</tt> itself defines the two methods).
 
* Implicit conversion to supertypes (<tt>class</tt> or <tt>interface</tt>) is allowed ONLY if the supertype is also a reference counted type. It follows that reference counted types cannot be converted to <tt>Object</tt> (unless <tt>Object</tt> itself defines the two methods).
  
* Explicit casting to <tt>void*</tt> does not entail a call to <tt>AddRef</tt>.
+
* Explicit casting to <tt>void*</tt> does not entail a call to <tt>opAddRef</tt>.
  
 
* Typechecking methods of reference counted types is done the same as for <tt>struct</tt>s. This is important because it limits what reference counted types. Consider:
 
* Typechecking methods of reference counted types is done the same as for <tt>struct</tt>s. This is important because it limits what reference counted types. Consider:
Line 105: Line 105:
 
     ref int getData1() { return data; } // ERROR
 
     ref int getData1() { return data; } // ERROR
 
     ref int getData2() return { return data; } // fine
 
     ref int getData2() return { return data; } // fine
     ulong AddRef();
+
     ulong opAddRef();
     ulong Release();
+
     ulong opRelease();
 
     ...
 
     ...
 
}
 
}
Line 115: Line 115:
 
== Defining a non-copyable reference type ==
 
== Defining a non-copyable reference type ==
  
Using <tt>@disable this(this);</tt> is a known idiom for creating <tt>struct</tt> objects that can be created and moved but not copied. The same is achievable with reference counted references by means of <tt>@disable AddRef();</tt>
+
Using <tt>@disable this(this);</tt> is a known idiom for creating <tt>struct</tt> objects that can be created and moved but not copied. The same is achievable with reference counted references by means of <tt>@disable opAddRef();</tt>
  
 
== Defining a reference counted object with deallocation ==
 
== Defining a reference counted object with deallocation ==
  
Classic reference counting techniques can be used with <tt>AddRef</tt> and <tt>Release</tt>.
+
Classic reference counting techniques can be used with <tt>opAddRef</tt> and <tt>opRelease</tt>.
  
 
<syntaxhighlight lang=D>
 
<syntaxhighlight lang=D>
 
class Widget {
 
class Widget {
 
     private uint _refs = 1;
 
     private uint _refs = 1;
     void AddRef() {
+
     void opAddRef() {
 
         ++refs;
 
         ++refs;
 
     }
 
     }
     void Release() {
+
     void opRelease() {
 
         if (refs > 1) {
 
         if (refs > 1) {
 
             --refs;
 
             --refs;

Revision as of 18:39, 26 February 2015

Title: Safe Reference Counted Class Objects
DIP: 74
Version: 1
Status: Draft
Created: 2015-02-23
Last Modified: 2015-02-26
Author: Walter Bright and Andrei Alexandrescu

Abstract

This DIP proposes @safe reference counted class objects for D (including exceptions).

Description

DIP25 allows defining struct types that own data and expose references to it, @safely, whilst controlling lifetime of that data. This proposal allows defining class objects that are safe yet use deterministic destruction for themselves and resources they own.

The compiler detects automatically and treats specially all classes and interfaces that define the following two methods:

class Widget {
    T1 opAddRef();
    T2 opRelease();
    ...
}

T1 and T2 may be any types (usually void or an integral type). The methods may or may not be final or inherited. Any attributes are allowed on these methods. They must be public. UFCS-expanded calls are not acceptable. If these two methods exist and are accessible, the compiler categorizes this class or interface type as a reference counted reference and treats it as follows:

  • Whenever a new reference to an object is created (e.g. auto a = b;, compiler inserts a call to opAddRef in the generated code. Call is inserted only if the reference is not null. The lowering of auto a = b; is conceptually if (b) b.opAddRef(); auto a = b;
  • There is no call inserted for the first reference created via a constructor (i.e. it is assumed the constructor already puts the object in the appropriate state). For example the lowering of auto a = new Widget; does not insert a call to opAddRef.
  • Whenever a reference to an object is assigned (e.g. a = b), first b.opAddRef() is called and then a.opRelease() is called, followed by the reference assignment itself. Calls are only made if the respective objects are not null. So the lowering of e.g. a = b; is if (b) b.opAddRef(); if (a) a.opRelease(); a = b;
  • Whenever a reference to an object goes out of scope, the compiler inserts an implicit call to opRelease. Call is inserted only if the reference is not null.
  • The pass-by-value protocol for reference counted references is as follows: the caller calls opAddRef and the callee calls opRelease. These calls are sequenced and handled the same as copy constructor calls and destructor calls, respectively, for struct objects. Example:
struct A {
    this(this);
    ~this();
}
void fun(A x, Widget y, A z) {
}

In the code above, calling fun entails the sequence:

  1. All parameters are memcpy'd
  2. Postblit call for x
  3. y.opAddRef()
  4. Postblit call for z
  5. Function is entered
  6. Destructor call for z
  7. y.opRelease()
  8. Destructor call for x
  9. Function returns
  • Functions that return a reference counted reference call opAddRef against the returned reference, EXCEPT if the returned reference is an rvalue or a local variable. Functions that return a local variable do not call opRelease against it.
  • The compiler considers that opRelease is the inverse of opAddRef, and therefore is at liberty to elide pairs of calls to opAddRef/opRelease. Example:
Widget fun() {
    auto a = new Widget;
    auto b = a;
    return b;
}

Applying the rules defined above would have fun's lowering insert one call to opAddRef (for creating b) and one call to opRelease (when a goes out of scope). However, these calls may be elided.

  • Implicit conversion to supertypes (class or interface) is allowed ONLY if the supertype is also a reference counted type. It follows that reference counted types cannot be converted to Object (unless Object itself defines the two methods).
  • Explicit casting to void* does not entail a call to opAddRef.
  • Typechecking methods of reference counted types is done the same as for structs. This is important because it limits what reference counted types. Consider:
@safe class Widget1 {
    private int data;
    ref int getData() { return data; } // fine
    ...
}

@safe class Widget2 {
    private int data;
    ref int getData1() { return data; } // ERROR
    ref int getData2() return { return data; } // fine
    ulong opAddRef();
    ulong opRelease();
    ...
}

This is because it is safe for a garbage collected object to escape references to its internal state. The same is not allowed for reference counted objects because they are expected to be deallocated in a deterministic manner (same as e.g. struct objects on the stack).

Defining a non-copyable reference type

Using @disable this(this); is a known idiom for creating struct objects that can be created and moved but not copied. The same is achievable with reference counted references by means of @disable opAddRef();

Defining a reference counted object with deallocation

Classic reference counting techniques can be used with opAddRef and opRelease.

class Widget {
    private uint _refs = 1;
    void opAddRef() {
        ++refs;
    }
    void opRelease() {
        if (refs > 1) {
            --refs;
        } else {
            this.destroy();
            GC.free(cast(void*) this);
        }
    }
   ...
}

Usually such approaches also use private constructors and object factory to ensure the same allocation method is used during creation and destruction of the object.

If the object only needs to free this (and no other owned resources), the typechecking ensured by the compiler is enough to verify safety (however, @trusted needs to be applied to the call that frees this).

Defining a type that owns resources

TODO

Copyright

This document has been placed in the Public Domain.