14

I have the following code. How can I resolve the error in the last line?

protocol Animal {
    func walk()
}

struct Cat: Animal {
    func walk() {}

    init() { }
}

var obj: Any = Cat()
var cat = obj as Animal // ERROR: cannot downcast from Any to unrelated type Animal
Khanh Nguyen
  • 11,112
  • 10
  • 52
  • 65
  • re: the error you are seeing on your last line, it's showing up as `// ERROR: cannot downcast from Any to unrelated type Animal`, _not_ `Cat`, right? – fqdn Jun 19 '14 at 07:24

4 Answers4

13

Update: This has been fixed in Swift 1.2+ (Xcode 6.3+). The Xcode 6.3 beta release notes say:

Dynamic casts (“as!", “as?" and “is”) now work with Swift protocol types, so long as they have no associated types.


You can only check for protocol conformance (which includes is, as, and as?) with an @objc protocol. Animal is not @objc.

See the Checking for Protocol Conformance section of the Swift book.

NOTE

You can check for protocol conformance only if your protocol is marked with the @objc attribute

newacct
  • 119,665
  • 29
  • 163
  • 224
  • thanks for the information. But it is strange that the runtime must able to get type information to perform `Any -> Cat` cast so it should be able to figure out `Cat` conform `Animal`. I have no idea why this constraint exist. – Bryan Chen Jun 20 '14 at 00:59
  • This is a known limitation of Swift's runtime that's expected to be [lifted in a future version](https://devforums.apple.com/message/1072650#1072650). – mythz Jan 27 '15 at 00:41
  • That wording has been removed from the documentation (follow the link and you'll see). Now it only says that @objc marking is required for protocols with optional requirements. But I still get a compile error ("Cannot downcast from 'anyobject' to non-@objc protocol type 'blah') when I try checking for protocol conformance. Anyone know if the requirement has actually been lifted? – stone Apr 08 '15 at 00:44
  • 1
    @skypecakes: Yes, in the Xcode 6.3 beta (Swift 1.2) release notes, it mentions "Dynamic casts (“as!", “as?" and “is”) now work with Swift protocol types, so long as they have no associated types." If you use the released Xcode 6.2, it won't work. – newacct Apr 08 '15 at 08:37
  • @newacct can you confirm this please? I'm looking at a playground example in Xcode 7.0.1 Swift 2.0 that fails a conditional cast. – Max MacLeod Sep 30 '15 at 08:15
  • `so long as they have no associated types` is the problem area for me! – Chris Prince Jan 24 '17 at 04:31
3

You can workaround it by doing

 var cat = obj as Cat as Animal

but this workaround is almost useless... because you need to know the type of obj first


Edit:

As @newacct point out, it is not bug, see his answer for more information

xcrun swift
Welcome to Swift!  Type :help for assistance.
  1>
  2> @objc protocol Animal {
  3.         func walk()
  4. }

@objc class Cat: Animal {
    func walk() {}

    init() { }
}

var obj: AnyObject = Cat()

var cat = obj as Animal
  5>
  6> @objc class Cat: Animal {
  7.         func walk() {}
  8.
  9.         init() { }
 10. }
 11>
 12> var obj: AnyObject = Cat()
obj: Cat = {}
 13>
 14> var cat = obj as Animal
cat: Cat = {}
 15>

Animal protocol need @objc attribute and Cat need to be @objc class.

Bryan Chen
  • 45,816
  • 18
  • 112
  • 143
  • I don't think it's a bug, I think it would throw the type system into an ontological crisis if you could cast objects to a protocol. (Notwithstanding the fact it's possible in ObjC) – iluvcapra Jun 19 '14 at 06:00
  • I would encourage anyone who has run into this to [submit a bug to apple](https://bugreport.apple.com) – drewag Jun 19 '14 at 06:01
  • @iluvcapra anything can conform to protocol. and `Any` is just `protocol<>`. I can't see why we can't cast `Any` to `Animal`. also Type and Protocol are in same namespace in Swift, which encourage people to mix the usage of them. – Bryan Chen Jun 19 '14 at 06:02
  • Right but casting can only affect a variable's type, and protocols aren't types. Protocols validate types, they're prior to types, but cannot themselves form complete types. A necessary condition of types is ivar slots, and protocols don't have ivars. – iluvcapra Jun 19 '14 at 06:06
  • @iluvcapra @_@ I am lost... and what do you mean by "A necessary condition of types is ivar slots". I don't think they are related – Bryan Chen Jun 19 '14 at 06:16
  • Sorry I'm being obtuse- types can be reified, a protocol cannot, a datatype can occupy a variable and have 1 and only 1 type. Protocols validate types, they aren't themselves types. If you could cast an object to a protocol, that would require that protocols themselves could trigger runtime errors, and that would ruin type-safety. ObjC has the concept of bottom-type objects that conform to protocols, but in a type-safe language, a named item can have one and only one type, and a bottom type that conforms to a protocol isn't this. – iluvcapra Jun 19 '14 at 06:19
  • It's not a bug. You can only check for conformance to `@objc` protocols. – newacct Jun 20 '14 at 00:09
0

As far as I have been able to observe, AnyObject can only be downcast to an instances of class types(†) and Any can only be downcast to types (class or otherwise), it is not possible to cast them to a protocol.

I don't know if I would qualify this as a bug or not, if this holds then perhaps it was implemented this way for a good reason - but @Bryan's workaround (casting to a type first and then to a protocol) at least "resolves the error"!

(†) notable exceptions include being able to downcast from AnyObject to the core data types Int, String, etc.

fqdn
  • 2,823
  • 1
  • 13
  • 16
  • "notable exceptions include being able to downcast from AnyObject to the core data types Int, String, etc." This is due to bridging of Foundation types. If you don't `import Foundation` those casts won't compile. – newacct Jun 20 '14 at 01:33
0

My approach

@objc protocol Animal {
    func walk()
}

@objc class DummyAnimal: Animal {
    func walk() {

    }
}

@objc class Cat: DummyAnimal {
    override func walk() {
        print("meow")
    }

    override init() { }
}

var obj: Any = Cat()
var cat = obj as DummyAnimal
cat.walk()
林煒峻
  • 1
  • 1