Clonable

Author:Dave Abrahams
Author:Joe Groff
Date:2013-03-21
Edition:2

Warning

This proposal was rejected. We decided not to introduce a language-level copying mechanism for classes.

Abstract: to better support the creation of value types, we propose a “magic” Clonable protocol and an annotation for describing which instance variables should be cloned when a type is copied. This proposal augments revision 1 of the Clonable proposal with our rationale for dropping our support for val and ref, a description of the programming model for generics, and a brief discussion of equality. It is otherwise unchanged.

Rationale

By eliminating val, we lose the easy creation of runtime-polymorphic value types. Instead of merely writing:

val x : MyClass

one has to engage in some kind of wrapping and forwarding:

struct MyClassVal {
  var [clone] value : MyClass

  constructor(x : A, y : B) {
     value = new MyClass(x, y)
  }

  func someFunction(z : C) -> D {
    return value.someFunction(z)
  }

  // ...etc...
}

Although such wrapping is awful, this is not the only place where one would want to do it. Therefore, some kind of ability to forward an entire interface wholesale could be added as a separate extension (getter/setter for This?), which would solve more problems. Then it would be easy enough to write the wrapper as a generic struct and Val<T> would be a reality.

By eliminating ref, we lose the easy creation of references to value types. However, among those who prefer programming with values, having an explicit step for dereferencing might make more sense, so we could use this generic class:

class Reference<T> { value : T }

If explicit dereferencing isn’t desired, there’s always manual (or automatic, if we add that feature) forwarding.

By dropping val we also lose some terseness aggregating class contents into structs. However, since ref is being dropped there’s less call for a symmetric val. The extra “cruft” that [clone] adds actually seems appropriate when viewed as a special bridge for class types, and less like a penalty against value types.

Generics

There is actually a straightforward programming model for generics. If you want to design a generic component where a type parameter T binds to both classes and non-class types, you can view T as a value type where—as with C pointers—the value is the reference rather than the object being referred to.

Of course, if T is only supposed to bind to classes, a different programming model may work just as well.

Implications for Equality

We think the programming model suggested for generics has some pretty strong implications for equality of classes: a == b must return true iff a and b refer to the same object.

Details (unchanged from Revision 1)

When a type with reference semantics R is to be used as a part of (versus merely being referred-to-by) a type with value semantics V, a new annotation, [clone] can be used to indicate that the R instance variable should be clone()d when V is copied.

A class can be clone()d when it implements the built-in Clonable protocol:

protocol Clonable {
   func clone() -> Self { /* see below */ }
}

The implementation of clone() (which will be generated by the compiler and typically never overridden) performs a primitive copy of all ordinary instance variables, and a clone() of all instance variables marked [clone]:

class FooValue : Clonable  {}

class Bar {}

class Foo : Clonable {
    var count : Int
    var [clone] myValue : FooValue
    var somethingIJustReferTo : Bar
}

struct Baz {
    var [clone] partOfMyValue : Foo
    var anotherPart : Int
    var somethingIJustReferTo : Bar
}

When a Baz is copied by any of the “big three” operations (variable initialization, assignment, or function argument passing), even as part of a larger struct, its [clone] member is clone()d. Because Foo itself has a [clone] member, that is clone()d also. Therefore copying a Baz object clone()s a Foo and clone()ing a Foo clone()s a FooValue.

All structs are Clonable by default, with clone() delivering ordinary copy semantics. Therefore,

var x : Baz
var y = x.clone()

is equivalent to

var x : Baz
var y = x

Note that Clonable is the first protocol with a default implementation that can’t currently be written in the standard library (though arguably we’d like to add the capability to write that implementation).