5

If I have two protocols whose associated type happens to be the same, such as

protocol Read {
    associatedtype Element
    func read() -> Element
}
protocol Write {
    associatedtype Element
    func write(a: Element)
}

Then I would like to have a class to read integer from and write string to:

class ReadWrite: Read, Write {
    func read() -> Int {
        return 5
    }
    func write(a: String) {
        print("writing \(a)")
    }
}

but the compiler complains and suggests changing String to Int. Ideally the type should be inferred, or at least compiles if I explicitly declare

associatedtype Read.Element = Int
associatedtype Write.Element = String

within ReadWrite. Any work around?

update

Workaround inspired by this question is to create two auxiliary protocols

protocol ReadInt: Read {
    associatedtype Element = Int
}
protocol WriteString: Write {
    associatedtype Element = String
}

and have the class inherit from these two instead:

class ReadWrite: ReadInt, WriteString {
    func read() -> Int {
        return 5
    }
    func write(a: String) {
        print("writing \(a)")
    }
}

This seems to compile, but I am afraid of any gotcha following this way.

update again

I found the issue in Swift's issue tracker. Anyone require this missing feature (like me) should vote for it. As a comparison, this pattern is possible in Rust, which also supports associated types (although this is not an idiomatic usage).

Franklin Yu
  • 8,920
  • 6
  • 43
  • 57
  • Do you own the Read and Write protocols? If so, you could simply change the name of the associatedtypes to something like ReadElement and WriteElement. – Mike Taverne Jun 09 '16 at 23:18
  • @MikeTaverne true, but I feel stressed when I am writing libraries, where I need to manually prefix the name of my associated type (like `FYMyLibElement`. This issue is not that rare, e.g. in the Foundation package there are many protocols sharing the same associated type name "Element". – Franklin Yu Jun 10 '16 at 02:41
  • Is it possible to implement two such Foundation protocols in the same class where a different type is used in the implementation of each protocol? – Mike Taverne Jun 10 '16 at 05:09
  • Still an issue in Swift 3 / Xcode 8. Did you consider filing a bug report? – Mike Taverne Jun 15 '16 at 22:26
  • 1
    Hmm... Not yet. Should I file it [here](https://bugs.swift.org/issues)? Oh, there is already [an issue](https://bugs.swift.org/browse/SR-1616). – Franklin Yu Jun 16 '16 at 04:00
  • 1
    @MikeTaverne I voted for the issue; that's all I can do by far, I guess. – Franklin Yu Jun 16 '16 at 04:07
  • I think, there is a general problem in the language: it lacks means to use qualified names. The described problem is just _one_ of many issues that arise due to name conflicts which could be easily solved when there were the possibility to use _qualified_ names. By the way, I'm the original poster of the issue in the Swift issue tracker. Please vote it up, if you want to improve the language ;) – CouchDeveloper Jun 16 '16 at 07:16
  • @CouchDeveloper I am not sure whether {the compiler does use qualified names under the hood, but it does not expose direct access to it from Swift code} or {the compiler does not use qualified names at all}. If it is the latter, I would say, this feature (associated type) is not carefully designed. This problem never happened in solid languages such as Java or C++. However, I do remember seeing something like `Self.Generator.Element` somewhere, so I believe that we just need to ask for direct access to the qualified name. – Franklin Yu Jun 16 '16 at 20:36
  • @FranklinYu Totally agree! – CouchDeveloper Jun 17 '16 at 06:50
  • Having many protocols in a project, these seem to be a dangerous limitation. I added my vote to the issue - thanks for providing the link. – Steffen D. Sommer Aug 10 '16 at 14:13

1 Answers1

1

Another workaround is to create a third, combined protocol:

protocol ReadWrite {
    associatedtype R
    associatedtype W
    func read() -> R
    func write(a: W)
}

It's not pretty, since it forces you to redeclare the protocol members, but it does keep it generic (you're not limited to String and Int).

Mike Taverne
  • 9,156
  • 2
  • 42
  • 58
  • This solution reminds me of [this question](http://stackoverflow.com/q/33968066); I tend to regard this as a bug of the compiler. – Franklin Yu Jun 10 '16 at 20:57
  • This solution has one drawback: if more than two protocols share the same associated type name, I need to have a combined protocol for every possible combination among them, so I would leave the choice to the user. – Franklin Yu Jun 10 '16 at 21:26
  • It seems the compiler cannot distinguish between associatedtypes defined in different protocols with the same name. The scope of the associatedtype name is made to extend beyond the protocol in which it was defined. – Mike Taverne Jun 10 '16 at 23:55
  • I thought that under the hood `Element` is renamed to `Read.Element` or `Write.Element`. It seems that currently the compiler is polluting the current namespace with all the associated type from the adopted protocols (and their parent-protocols), is it? Or this is just how the language is specified? – Franklin Yu Jun 11 '16 at 19:19
  • It's not really a workaround: suppose, there are two APIs one requiring a parameter conforming to `Read` another one with a parameter conforming to `Write`. There's no way to have _one_ concrete struct or class which conforms to both. "No way" means, it's impossible to express this in Swift. Bummer. – CouchDeveloper Jun 16 '16 at 07:21
  • @CouchDeveloper You're right, it's not a workaround for the consumer. I was suggesting as a workaround if you are the author of the protocols. Although an easier solution in that case, which I also mentioned, is to name your associatedtypes uniquely. – Mike Taverne Jun 16 '16 at 15:49