Pitch: Copy constructors for structs


(Charles Srstka) #1

Introduction:

This is a request for a copy constructor mechanism for structs in Swift.

Motivation:

Suppose you have a class stored inside a struct, like so:

class C {
  func copy() -> C { … }
}

struct S {
  var i: Int
  var c: C
}

and you create a couple of the structs, like so:

let c = C()
let foo = S(i: 1, c: c)
var bar = foo
bar.i = 2

Since the ‘bar’ variable was mutated, it now contains a copy of the original ‘foo’ struct. However, both structs still carry the same pointer to ‘c'. There may be cases where you would want a copy of the struct to make a copy of any reference types stored within; however, that does not seem to be possible currently.

Proposed Solution:

Adding a copy constructor to S that would be called when a copy of the struct is about to be made. This constructor would simply create a new instance, initialize it, and return it. The copy constructor would look like this:

struct S {
  var i: Int
  var c: C

  copy {
    return S(i: self.i, c: self.c.copy())
  }
}

Structs that do not implement the copy constructor would get the same behavior as they do currently.

Impact on Existing Code:

There should be no impact on existing code that does not implement the copy constructor.

Charles


(Joe Groff) #2

Currently, in most places where this is desired, we treat the reference type's interface as part of the value's interface and use copy-on-write to manage the referenced storage, as with arrays, dictionaries, and sets. Eager copying by a C++-like mechanism is interesting, and we designed the runtime with an eye toward future C++ interop, but this still has pretty massive impacts on the rest of the language and implementation that need deeper consideration than "no impact on existing code". When exactly are copy constructors run? When, if ever, is the compiler allowed to elide copies? Is move optimization possible? Do you have destructors, and if so, when are they run? How and when do we know copying a shared value can be done in a thread-safe way? In addition to looking at the C++ model, I'd also recommend thinking about simply allowing custom retain/release of otherwise bitwise-identical copies, or something like D's "post-blit constructor", the design of which avoids some of the pitfalls of C++.

-Joe

···

On Dec 23, 2015, at 1:03 PM, Charles Srstka via swift-evolution <swift-evolution@swift.org> wrote:

Introduction:

This is a request for a copy constructor mechanism for structs in Swift.

Motivation:

Suppose you have a class stored inside a struct, like so:

class C {
  func copy() -> C { … }
}

struct S {
  var i: Int
  var c: C
}

and you create a couple of the structs, like so:

let c = C()
let foo = S(i: 1, c: c)
var bar = foo
bar.i = 2

Since the ‘bar’ variable was mutated, it now contains a copy of the original ‘foo’ struct. However, both structs still carry the same pointer to ‘c'. There may be cases where you would want a copy of the struct to make a copy of any reference types stored within; however, that does not seem to be possible currently.

Proposed Solution:

Adding a copy constructor to S that would be called when a copy of the struct is about to be made. This constructor would simply create a new instance, initialize it, and return it. The copy constructor would look like this:

struct S {
  var i: Int
  var c: C

  copy {
    return S(i: self.i, c: self.c.copy())
  }
}

Structs that do not implement the copy constructor would get the same behavior as they do currently.

Impact on Existing Code:

There should be no impact on existing code that does not implement the copy constructor.

Charles

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Félix Cloutier) #3

The "when exactly are copy constructors run" and "when is the compiler allowed to elide copies" parts are very important and not always obvious in current Swift code. For instance:

struct Point {
  var x: Int
  var y: Int
}

class Foo {
  var point: Point
}

let foo = Foo()
foo.point.x = 4

This code "expands" to:

var point = foo.point
point.x = 4
foo.point = point

in which you have two copies, whereas in C++ you would have none.

Félix

···

Le 23 déc. 2015 à 16:25:50, Joe Groff via swift-evolution <swift-evolution@swift.org> a écrit :

Currently, in most places where this is desired, we treat the reference type's interface as part of the value's interface and use copy-on-write to manage the referenced storage, as with arrays, dictionaries, and sets. Eager copying by a C++-like mechanism is interesting, and we designed the runtime with an eye toward future C++ interop, but this still has pretty massive impacts on the rest of the language and implementation that need deeper consideration than "no impact on existing code". When exactly are copy constructors run? When, if ever, is the compiler allowed to elide copies? Is move optimization possible? Do you have destructors, and if so, when are they run? How and when do we know copying a shared value can be done in a thread-safe way? In addition to looking at the C++ model, I'd also recommend thinking about simply allowing custom retain/release of otherwise bitwise-identical copies, or something like D's "post-blit constructor", the design of which avoids some of the pitfalls of C++.

-Joe

On Dec 23, 2015, at 1:03 PM, Charles Srstka via swift-evolution <swift-evolution@swift.org> wrote:

Introduction:

This is a request for a copy constructor mechanism for structs in Swift.

Motivation:

Suppose you have a class stored inside a struct, like so:

class C {
  func copy() -> C { … }
}

struct S {
  var i: Int
  var c: C
}

and you create a couple of the structs, like so:

let c = C()
let foo = S(i: 1, c: c)
var bar = foo
bar.i = 2

Since the ‘bar’ variable was mutated, it now contains a copy of the original ‘foo’ struct. However, both structs still carry the same pointer to ‘c'. There may be cases where you would want a copy of the struct to make a copy of any reference types stored within; however, that does not seem to be possible currently.

Proposed Solution:

Adding a copy constructor to S that would be called when a copy of the struct is about to be made. This constructor would simply create a new instance, initialize it, and return it. The copy constructor would look like this:

struct S {
  var i: Int
  var c: C

  copy {
    return S(i: self.i, c: self.c.copy())
  }
}

Structs that do not implement the copy constructor would get the same behavior as they do currently.

Impact on Existing Code:

There should be no impact on existing code that does not implement the copy constructor.

Charles

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(John McCall) #4

The "when exactly are copy constructors run" and "when is the compiler allowed to elide copies" parts are very important and not always obvious in current Swift code. For instance:

struct Point {
  var x: Int
  var y: Int
}

class Foo {
  var point: Point
}

let foo = Foo()
foo.point.x = 4

This code "expands" to:

var point = foo.point
point.x = 4
foo.point = point

in which you have two copies, whereas in C++ you would have none.

As a point of fact, no: when the class property is actually stored, the modifications are dynamically performed in place due to the materializeForSet optimization.

John.

···

On Dec 23, 2015, at 2:18 PM, Félix Cloutier via swift-evolution <swift-evolution@swift.org> wrote:

Félix

Le 23 déc. 2015 à 16:25:50, Joe Groff via swift-evolution <swift-evolution@swift.org> a écrit :

Currently, in most places where this is desired, we treat the reference type's interface as part of the value's interface and use copy-on-write to manage the referenced storage, as with arrays, dictionaries, and sets. Eager copying by a C++-like mechanism is interesting, and we designed the runtime with an eye toward future C++ interop, but this still has pretty massive impacts on the rest of the language and implementation that need deeper consideration than "no impact on existing code". When exactly are copy constructors run? When, if ever, is the compiler allowed to elide copies? Is move optimization possible? Do you have destructors, and if so, when are they run? How and when do we know copying a shared value can be done in a thread-safe way? In addition to looking at the C++ model, I'd also recommend thinking about simply allowing custom retain/release of otherwise bitwise-identical copies, or something like D's "post-blit constructor", the design of which avoids some of the pitfalls of C++.

-Joe

On Dec 23, 2015, at 1:03 PM, Charles Srstka via swift-evolution <swift-evolution@swift.org> wrote:

Introduction:

This is a request for a copy constructor mechanism for structs in Swift.

Motivation:

Suppose you have a class stored inside a struct, like so:

class C {
  func copy() -> C { … }
}

struct S {
  var i: Int
  var c: C
}

and you create a couple of the structs, like so:

let c = C()
let foo = S(i: 1, c: c)
var bar = foo
bar.i = 2

Since the ‘bar’ variable was mutated, it now contains a copy of the original ‘foo’ struct. However, both structs still carry the same pointer to ‘c'. There may be cases where you would want a copy of the struct to make a copy of any reference types stored within; however, that does not seem to be possible currently.

Proposed Solution:

Adding a copy constructor to S that would be called when a copy of the struct is about to be made. This constructor would simply create a new instance, initialize it, and return it. The copy constructor would look like this:

struct S {
  var i: Int
  var c: C

  copy {
    return S(i: self.i, c: self.c.copy())
  }
}

Structs that do not implement the copy constructor would get the same behavior as they do currently.

Impact on Existing Code:

There should be no impact on existing code that does not implement the copy constructor.

Charles

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Félix Cloutier) #5

At which point does that happen? The O0 SIL for it has an alloc_stack $Point in main.

Otherwise, that's hard-to-predict copy elision at work anyway.

Félix

···

Le 24 déc. 2015 à 14:40:13, John McCall <rjmccall@apple.com> a écrit :

On Dec 23, 2015, at 2:18 PM, Félix Cloutier via swift-evolution <swift-evolution@swift.org> wrote:
The "when exactly are copy constructors run" and "when is the compiler allowed to elide copies" parts are very important and not always obvious in current Swift code. For instance:

struct Point {
  var x: Int
  var y: Int
}

class Foo {
  var point: Point
}

let foo = Foo()
foo.point.x = 4

This code "expands" to:

var point = foo.point
point.x = 4
foo.point = point

in which you have two copies, whereas in C++ you would have none.

As a point of fact, no: when the class property is actually stored, the modifications are dynamically performed in place due to the materializeForSet optimization.

John.

Félix

Le 23 déc. 2015 à 16:25:50, Joe Groff via swift-evolution <swift-evolution@swift.org> a écrit :

Currently, in most places where this is desired, we treat the reference type's interface as part of the value's interface and use copy-on-write to manage the referenced storage, as with arrays, dictionaries, and sets. Eager copying by a C++-like mechanism is interesting, and we designed the runtime with an eye toward future C++ interop, but this still has pretty massive impacts on the rest of the language and implementation that need deeper consideration than "no impact on existing code". When exactly are copy constructors run? When, if ever, is the compiler allowed to elide copies? Is move optimization possible? Do you have destructors, and if so, when are they run? How and when do we know copying a shared value can be done in a thread-safe way? In addition to looking at the C++ model, I'd also recommend thinking about simply allowing custom retain/release of otherwise bitwise-identical copies, or something like D's "post-blit constructor", the design of which avoids some of the pitfalls of C++.

-Joe

On Dec 23, 2015, at 1:03 PM, Charles Srstka via swift-evolution <swift-evolution@swift.org> wrote:

Introduction:

This is a request for a copy constructor mechanism for structs in Swift.

Motivation:

Suppose you have a class stored inside a struct, like so:

class C {
  func copy() -> C { … }
}

struct S {
  var i: Int
  var c: C
}

and you create a couple of the structs, like so:

let c = C()
let foo = S(i: 1, c: c)
var bar = foo
bar.i = 2

Since the ‘bar’ variable was mutated, it now contains a copy of the original ‘foo’ struct. However, both structs still carry the same pointer to ‘c'. There may be cases where you would want a copy of the struct to make a copy of any reference types stored within; however, that does not seem to be possible currently.

Proposed Solution:

Adding a copy constructor to S that would be called when a copy of the struct is about to be made. This constructor would simply create a new instance, initialize it, and return it. The copy constructor would look like this:

struct S {
  var i: Int
  var c: C

  copy {
    return S(i: self.i, c: self.c.copy())
  }
}

Structs that do not implement the copy constructor would get the same behavior as they do currently.

Impact on Existing Code:

There should be no impact on existing code that does not implement the copy constructor.

Charles

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Joe Groff) #6

The alloc_stack is passed into materializeForSet, but it's not required to take advantage of it. materializeForSet will either initialize the stack space with the temporary result of `get` from a computed property and return the stack address back, or leave it uninitialized and return the original physical address of a stored property.

-Joe

···

On Dec 24, 2015, at 1:04 PM, Félix Cloutier <felixcca@yahoo.ca> wrote:

At which point does that happen? The O0 SIL for it has an alloc_stack $Point in main.

Otherwise, that's hard-to-predict copy elision at work anyway.


(Félix Cloutier) #7

Oh well. I thought I had done due diligence! One less thing to be wrong about in the future.

Félix

···

Le 24 déc. 2015 à 16:07:25, Joe Groff <jgroff@apple.com> a écrit :

On Dec 24, 2015, at 1:04 PM, Félix Cloutier <felixcca@yahoo.ca> wrote:

At which point does that happen? The O0 SIL for it has an alloc_stack $Point in main.

Otherwise, that's hard-to-predict copy elision at work anyway.

The alloc_stack is passed into materializeForSet, but it's not required to take advantage of it. materializeForSet will either initialize the stack space with the temporary result of `get` from a computed property and return the stack address back, or leave it uninitialized and return the original physical address of a stored property.

-Joe