DIP35

From D Wiki
Revision as of 06:54, 11 April 2013 by ZachTheMystic (talk | contribs) (The 'out' Function Attribute)
Jump to: navigation, search

DIP35: Addition to DIP25: Sealed References

Title: Sealed References Amendment
DIP: 35
Version: 1
Status: Draft
Created: 2013-04-07
Last Modified: 2013-04-07
Author: Zach the Mystic
Links: http://wiki.dlang.org/DIP25

Abstract

DIP25: Sealed References makes important inroads toward safe references in D. It consists of two parts, the first one about making 'ref' safe, and the second about '&' and 'addressOf'. The present DIP focuses mainly on the first part, proposing using 'out' as a function attribute and 'scope' parameters to add flexibility to the restrictions imposed by DIP25, but also makes a small proposal regarding the '&' operator.

In this proposal, I will assume that the compiler tracks the locality of each variable with three categories: 'local', which is known to be stack-allocated; 'global', which means static or heap-allocated; and 'reference parameter', which is unknown, but could be either. (The eventual intent should be to group every action involving references into one of the three categories: '@safe', 'error', and '@system'.)

'scope' Parameters

ref T func(ref T p) {
  T a;          // locality: 'local'
  static T b;   // locality: 'global'
  p;            // locality: 'ref parameter'

  return a; // Error: a local may not be returned by ref
  return b; // @safe: returning a global by ref
  return p; // @safe, so long as caller keeps track of result!
  return *new T; // @safe, heap == global
}

Above, the reference parameter 'p' may be returned. The safety measures suggested by DIP25 will ensure that the return value will not itself be escaped if a local was passed in. This allows the "identity" function to work perfectly. But what if you wanted to create a "copy" function, which was guaranteed to return a non-local, regardless of what was passed in?

ref T identity(ref T a) { return a; }
ref T copy(ref T p) {
  T* t = new T;
  *t = p;
  return *t;
}

ref T testFuncs() {
  T t;
  return identity(t); // Error, as it should
  return copy(t);     // @safe, but error according to DIP25
}

'ref' alone is not powerful enough to deal with this issue efficiently. Without any more information available to the caller, the result of 'copy(t)' must be assumed local, even if it's meant to be global. If you could mark each parameter with additional information, using the 'scope' attribute, the return value could be accurately identified as global, and because the information is found in the signature of the function, it is available both when being called and when compiling the function.

ref T copy(scope T a) {
  T* t = new T;
  *t = a;
  return *t;
  //return a; // Error: may not return a 'scope' parameter
}

ref T testCopy() {
  T t;
  return copy(t); // Pass: t is 'scope', return *must* be global
}

If a function has more than one parameter, 'scope' could be used to fine-tune what may or may not be returned from it:

ref T add(ref T a, scope T b) {
  a = a + b; // assume T implements opAdd
  return a;
  //return b; // Error: may not return 'scope' parameter
}

ref T test() {
  T c;
  static T d;
  //return add(c, d); // Error, 'c' is local
  return add(d, c); // Pass, only 'd' may be returned
}

The 'out' Function Attribute

While 'scope' parameters are attractive, marking a lot of parameters 'scope' might be tedious. One option is to imagine the 'out' keyword as a function attribute, guaranteeing that no reference may be escaped, in effect marking all parameters 'scope'. It is slightly more intuitive than 'scope', especially as 'scope' would be necessary to add outside the parameter list of member functions, to describe the hidden 'this' reference.

struct S {
  int t;
  ref S copyAdd(scope S p) scope {
    S* s = new S;
    s.t = t + p.t;
    return s;
  }
  // Same effect, but more intuitive
  out S copyAdd(ref S p) {
    S* s = new S;
    s.t = t + p.t;
    return s;
  }
}

One could even imagine foregoing 'scope' altogether in favor of 'out', although function parameters could not then be individually marked. Still, 'out' is a cosmetic addition and not strictly necessary. The minor problem of placing it after a declaration and confusing it with an 'out' contract is solved by either ensuring that 'out' is not the last attribute listed, or by inserting 'body' between 'out' and '{'.

T func(ref T a) out @trusted { return *new T; }
T func(ref T a) out body { return *new T; }

A final issue is that sometimes it is a class object or delegate which is passed to the function, in which case 'scope' and 'out' need not imply 'ref' but should still provide the relevant safety guarantee. Having 'scope' imply 'ref' may unnecessarily complicate the language.

The '&' operator and C Functions

This is a very minor additional suggestion regarding DIP25, and I don't expect it to resolve the whole issue. While '&' is generally dangerous, backward compatibility with C functions would make it inconvenient to disallow. Therefore, some value may be gained by permitting its use when calling C functions.

void func1(T* t) {}
extern (C) void func2(T* t) {}

void test() {
  T a;
  func1(&a); // Error, according to DIP25
  func2(&a); // Pass, for backward compatibility reasons
}

Copyright

This document has been placed in the Public Domain.