returning an array of Protocol-conforming objects from a function


(Ryan Walklin) #1

Hi All,

I have a function which returns a closure, which then returns an array of objects conforming to a protocol.

ie.

protocol MyProtocol {}

extension Float: MyProtocol {}
extension OtherType: MyProtocol {}

When I try to return an array of Floats, I get the error “Cannot convert value of type '[Float]' to closure result type ‘[MyProtocol]”.

Other struct-based types work fine.

Is this not possible with standard library types?

Regards,

Ryan


(Austin Zheng) #2

Hi Ryan,

Are you sure it works with user-defined struct types? I tried the following code, and got the same error:

protocol MyProtocol {}

struct Blah : MyProtocol { }

func foo() -> [MyProtocol] {
  let a = [Blah(), Blah()] as [Blah]
  return a
}

'class Blah' works fine.

Even so, this is not the behavior I would have expected. Perhaps someone else can explain why [T] can't be upcasted to [P] when T is a struct and T : P.

Best,
Austin

···

On Jan 25, 2016, at 7:36 PM, Ryan Walklin via swift-users <swift-users@swift.org> wrote:

Hi All,

I have a function which returns a closure, which then returns an array of objects conforming to a protocol.

ie.

protocol MyProtocol {}

extension Float: MyProtocol {}
extension OtherType: MyProtocol {}

When I try to return an array of Floats, I get the error “Cannot convert value of type '[Float]' to closure result type ‘[MyProtocol]”.

Other struct-based types work fine.

Is this not possible with standard library types?

Regards,

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


(Austin Zheng) #3

I'm reasonably sure Arrays and other collections are covariant, but any user-defined generic type is invariant. This came up in the discussion about making all generics covariant by default.

For some reason this works for me:

protocol MyProtocol {}
extension Float: MyProtocol {}

func foo() -> [MyProtocol] {
  let a = [Float(1), Float(2)] as [MyProtocol]
  return a
}

but this doesn't (with or without the cast):

func foo() -> [MyProtocol] {
  let a = [Float(1), Float(2)] //as [Float]
  return a as [MyProtocol]
}

Nor does this:

func foo() -> [MyProtocol] {
  let a = [Float(1), Float(2)] as [Float]
  let b = a as [MyProtocol]
  return b
}

This definitely looks like a bug, or at least something that needs a better compiler error message. (I'm running this in an OS X Xcode project with Swift 2.1, but it doesn't work with Swift 2.2 either.)

Best,
Austin

···

On Jan 25, 2016, at 7:51 PM, Trent Nadeau <tanadeau@gmail.com> wrote:

I think arrays along with other generics are invariant.

On Mon, Jan 25, 2016 at 10:50 PM, Trent Nadeau <tanadeau@gmail.com <mailto:tanadeau@gmail.com>> wrote:
That code works fine for me if I change the cast to "as [MyProtocol]".

protocol MyProtocol {}

struct Blah : MyProtocol { }

func foo() -> [MyProtocol] {
  let a = [Blah(), Blah()] as [MyProtocol]
  return a
}

On Mon, Jan 25, 2016 at 10:47 PM, Austin Zheng via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:
Hi Ryan,

Are you sure it works with user-defined struct types? I tried the following code, and got the same error:

protocol MyProtocol {}

struct Blah : MyProtocol { }

func foo() -> [MyProtocol] {
  let a = [Blah(), Blah()] as [Blah]
  return a
}

'class Blah' works fine.

Even so, this is not the behavior I would have expected. Perhaps someone else can explain why [T] can't be upcasted to [P] when T is a struct and T : P.

Best,
Austin

On Jan 25, 2016, at 7:36 PM, Ryan Walklin via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

Hi All,

I have a function which returns a closure, which then returns an array of objects conforming to a protocol.

ie.

protocol MyProtocol {}

extension Float: MyProtocol {}
extension OtherType: MyProtocol {}

When I try to return an array of Floats, I get the error “Cannot convert value of type '[Float]' to closure result type ‘[MyProtocol]”.

Other struct-based types work fine.

Is this not possible with standard library types?

Regards,

Ryan
_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users

_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users

--
Trent Nadeau

--
Trent Nadeau


(Trent Nadeau) #4

That code works fine for me if I change the cast to "as [MyProtocol]".

protocol MyProtocol {}

struct Blah : MyProtocol { }

func foo() -> [MyProtocol] {
  let a = [Blah(), Blah()] as [MyProtocol]
  return a
}

···

On Mon, Jan 25, 2016 at 10:47 PM, Austin Zheng via swift-users < swift-users@swift.org> wrote:

Hi Ryan,

Are you sure it works with user-defined struct types? I tried the
following code, and got the same error:

protocol MyProtocol {}

struct Blah : MyProtocol { }

func foo() -> [MyProtocol] {
  let a = [Blah(), Blah()] as [Blah]
  return a
}

'class Blah' works fine.

Even so, this is not the behavior I would have expected. Perhaps someone
else can explain why [T] can't be upcasted to [P] when T is a struct and T
: P.

Best,
Austin

On Jan 25, 2016, at 7:36 PM, Ryan Walklin via swift-users < > swift-users@swift.org> wrote:

Hi All,

I have a function which returns a closure, which then returns an array of
objects conforming to a protocol.

ie.

protocol MyProtocol {}

extension Float: MyProtocol {}
extension OtherType: MyProtocol {}

When I try to return an array of Floats, I get the error “Cannot convert
value of type '[Float]' to closure result type ‘[MyProtocol]”.

Other struct-based types work fine.

Is this not possible with standard library types?

Regards,

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

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

--
Trent Nadeau


(Trent Nadeau) #5

I think arrays along with other generics are invariant.

···

On Mon, Jan 25, 2016 at 10:50 PM, Trent Nadeau <tanadeau@gmail.com> wrote:

That code works fine for me if I change the cast to "as [MyProtocol]".

protocol MyProtocol {}

struct Blah : MyProtocol { }

func foo() -> [MyProtocol] {
  let a = [Blah(), Blah()] as [MyProtocol]
  return a
}

On Mon, Jan 25, 2016 at 10:47 PM, Austin Zheng via swift-users < > swift-users@swift.org> wrote:

Hi Ryan,

Are you sure it works with user-defined struct types? I tried the
following code, and got the same error:

protocol MyProtocol {}

struct Blah : MyProtocol { }

func foo() -> [MyProtocol] {
  let a = [Blah(), Blah()] as [Blah]
  return a
}

'class Blah' works fine.

Even so, this is not the behavior I would have expected. Perhaps someone
else can explain why [T] can't be upcasted to [P] when T is a struct and T
: P.

Best,
Austin

On Jan 25, 2016, at 7:36 PM, Ryan Walklin via swift-users < >> swift-users@swift.org> wrote:

Hi All,

I have a function which returns a closure, which then returns an array of
objects conforming to a protocol.

ie.

protocol MyProtocol {}

extension Float: MyProtocol {}
extension OtherType: MyProtocol {}

When I try to return an array of Floats, I get the error “Cannot convert
value of type '[Float]' to closure result type ‘[MyProtocol]”.

Other struct-based types work fine.

Is this not possible with standard library types?

Regards,

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

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

--
Trent Nadeau

--
Trent Nadeau


(Ryan Walklin) #6

Hi Austin,

Thanks for your reply. Sorry you’re right, my struct types don’t work if they are in in existing array of type [MyType] as below.

I’ve been trying to work round it and found that with the following code:

extension MyType: MyProtocol {}

var array1: [MyType]

fun foo() -> [MyProtocol] {
    return array1
}

func foo2 -> [MyProtocol] {
    return [myType1, myType2]
}

The foo pattern fails, but the foo2 pattern works. My existing code with struct type was all of type foo2, so I didn’t appreciate the error. As you stay, still seems like a problem with type inference.

Ryan

···

On 26 Jan 2016, at 2:47 PM, Austin Zheng <austinzheng@gmail.com> wrote:

Hi Ryan,

Are you sure it works with user-defined struct types? I tried the following code, and got the same error:

protocol MyProtocol {}

struct Blah : MyProtocol { }

func foo() -> [MyProtocol] {
  let a = [Blah(), Blah()] as [Blah]
  return a
}

'class Blah' works fine.

Even so, this is not the behavior I would have expected. Perhaps someone else can explain why [T] can't be upcasted to [P] when T is a struct and T : P.

Best,
Austin

On Jan 25, 2016, at 7:36 PM, Ryan Walklin via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

Hi All,

I have a function which returns a closure, which then returns an array of objects conforming to a protocol.

ie.

protocol MyProtocol {}

extension Float: MyProtocol {}
extension OtherType: MyProtocol {}

When I try to return an array of Floats, I get the error “Cannot convert value of type '[Float]' to closure result type ‘[MyProtocol]”.

Other struct-based types work fine.

Is this not possible with standard library types?

Regards,

Ryan
_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users


(Jordan Rose) #7

The problem is that an array of protocols is stored using a different representation from an array of plain structs (or classes, for that matter). Converting from [Float] to [MyProtocol] is therefore an O(N) operation, while a normal upcast between class types is O(1) (by sharing the underlying storage).

This does keep tripping people up, though, so maybe an explicit "as" should be considered good enough. The typical workarounds are either to use "as [MyProtocol]" to begin with (as Trent mentioned) or to use 'map' to build a new array ("return a.map { $0 }").

(Any change in this space would need to go through the Swift Evolution Process <https://github.com/apple/swift-evolution/blob/master/process.md>.)

Best,
Jordan

···

On Jan 25, 2016, at 19:58, Ryan Walklin via swift-users <swift-users@swift.org> wrote:

Hi Austin,

Thanks for your reply. Sorry you’re right, my struct types don’t work if they are in in existing array of type [MyType] as below.

I’ve been trying to work round it and found that with the following code:

extension MyType: MyProtocol {}

var array1: [MyType]

fun foo() -> [MyProtocol] {
    return array1
}

func foo2 -> [MyProtocol] {
    return [myType1, myType2]
}

The foo pattern fails, but the foo2 pattern works. My existing code with struct type was all of type foo2, so I didn’t appreciate the error. As you stay, still seems like a problem with type inference.

Ryan

On 26 Jan 2016, at 2:47 PM, Austin Zheng <austinzheng@gmail.com <mailto:austinzheng@gmail.com>> wrote:

Hi Ryan,

Are you sure it works with user-defined struct types? I tried the following code, and got the same error:

protocol MyProtocol {}

struct Blah : MyProtocol { }

func foo() -> [MyProtocol] {
  let a = [Blah(), Blah()] as [Blah]
  return a
}

'class Blah' works fine.

Even so, this is not the behavior I would have expected. Perhaps someone else can explain why [T] can't be upcasted to [P] when T is a struct and T : P.

Best,
Austin

On Jan 25, 2016, at 7:36 PM, Ryan Walklin via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

Hi All,

I have a function which returns a closure, which then returns an array of objects conforming to a protocol.

ie.

protocol MyProtocol {}

extension Float: MyProtocol {}
extension OtherType: MyProtocol {}

When I try to return an array of Floats, I get the error “Cannot convert value of type '[Float]' to closure result type ‘[MyProtocol]”.

Other struct-based types work fine.

Is this not possible with standard library types?

Regards,

Ryan
_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users

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


(Ryan Walklin) #8

Hi Jordan,

Thanks for your reply. I’ve since discovered the differences in memory representation that you mention, and the performance implications of the conversion.

I’ve worked around this by just using a typed array, as the eventual destination for the array is a Metal buffer via memcpy, so I need a contiguous array.

Regards,

Ryan

···

On 27 Jan 2016, at 5:56 AM, Jordan Rose <jordan_rose@apple.com> wrote:

The problem is that an array of protocols is stored using a different representation from an array of plain structs (or classes, for that matter). Converting from [Float] to [MyProtocol] is therefore an O(N) operation, while a normal upcast between class types is O(1) (by sharing the underlying storage).

This does keep tripping people up, though, so maybe an explicit "as" should be considered good enough. The typical workarounds are either to use "as [MyProtocol]" to begin with (as Trent mentioned) or to use 'map' to build a new array ("return a.map { $0 }").

(Any change in this space would need to go through the Swift Evolution Process <https://github.com/apple/swift-evolution/blob/master/process.md>.)

Best,
Jordan

On Jan 25, 2016, at 19:58, Ryan Walklin via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

Hi Austin,

Thanks for your reply. Sorry you’re right, my struct types don’t work if they are in in existing array of type [MyType] as below.

I’ve been trying to work round it and found that with the following code:

extension MyType: MyProtocol {}

var array1: [MyType]

fun foo() -> [MyProtocol] {
    return array1
}

func foo2 -> [MyProtocol] {
    return [myType1, myType2]
}

The foo pattern fails, but the foo2 pattern works. My existing code with struct type was all of type foo2, so I didn’t appreciate the error. As you stay, still seems like a problem with type inference.

Ryan

On 26 Jan 2016, at 2:47 PM, Austin Zheng <austinzheng@gmail.com <mailto:austinzheng@gmail.com>> wrote:

Hi Ryan,

Are you sure it works with user-defined struct types? I tried the following code, and got the same error:

protocol MyProtocol {}

struct Blah : MyProtocol { }

func foo() -> [MyProtocol] {
  let a = [Blah(), Blah()] as [Blah]
  return a
}

'class Blah' works fine.

Even so, this is not the behavior I would have expected. Perhaps someone else can explain why [T] can't be upcasted to [P] when T is a struct and T : P.

Best,
Austin

On Jan 25, 2016, at 7:36 PM, Ryan Walklin via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

Hi All,

I have a function which returns a closure, which then returns an array of objects conforming to a protocol.

ie.

protocol MyProtocol {}

extension Float: MyProtocol {}
extension OtherType: MyProtocol {}

When I try to return an array of Floats, I get the error “Cannot convert value of type '[Float]' to closure result type ‘[MyProtocol]”.

Other struct-based types work fine.

Is this not possible with standard library types?

Regards,

Ryan
_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users

_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users