This is a very odd example as Foo
and Bar
are just the same protocol with a different name. You could just write:
class Buz: Foo<Int>, Foo<String> {
func foo(foo: Int) {}
func foo(foo: String) {}
}
But that doesn't solve the problem because it would still not compile as Swift generics don't work with protocols in the first place.
I actually ran into this issue yesterday, so here is a more concrete example:
Swift version
public protocol HardwareDrawable<Renderer>: Drawable {
associatedtype Renderer: HardwareRenderer
func draw(into renderer: inout Renderer, x: Int, y: Int)
}
public protocol HardwareRenderer {}
public extension HardwareRenderer {
mutating func draw(_ drawable: some HardwareDrawable<Self>, x: Int, y: Int) {
drawable.draw(into: &self, x: x, y: y)
}
}
public struct SDLRenderer: HardwareRenderer {}
public struct OpenGLRenderer: HardwareRenderer {}
extension Rectangle: HardwareDrawable {
public func draw(into renderer: inout OpenGLRenderer, x: Int, y: Int) { ... }
}
extension Rectangle: HardwareDrawable {
public func draw(into renderer: inout SDLRenderer, x: Int, y: Int) { ... }
}
Rust version
pub trait HardwareDrawable<Renderer: HardwareRenderer>: Drawable {
fn draw(&self, renderer: &mut Renderer, x: usize, y: usize);
}
pub trait HardwareRenderer {
fn draw(&mut self, drawable: &mut impl HardwareDrawable<Self>, x: usize, y: usize) where Self: Sized {
drawable.draw(self, x, y)
}
}
pub struct SDLRenderer;
pub struct OpenGLRenderer;
impl HardwareRenderer for SDLRenderer {}
impl HardwareRenderer for OpenGLRenderer {}
impl HardwareDrawable<SDLRenderer> for Rectangle {
fn draw(&self, renderer: &mut SDLRenderer, x: usize, y: usize) { ... }
}
impl HardwareDrawable<OpenGLRenderer> for Rectangle {
fn draw(&self, renderer: &mut OpenGLRenderer, x: usize, y: usize) { ... }
}
It's kind of a weird protocol; to work within the constraints of a hardware renderer every hardware drawable type needs a custom implementation for every supported renderer, so for example you could imagine the SDLRenderer implementation of Rectangle
to be:
import SDL
public struct SDLRenderer: HardwareRenderer {
internal let handle: OpaquePointer
}
extension Rectangle: HardwareDrawable { // This will error if HardwareDrawable is already implemented for a different renderer.
public func draw(into renderer: inout SDLRenderer, x: Int, y: Int) {
var rect = SDL_Rect(x: Int32(x), y: Int32(y), w: Int32(self.width), h: Int32(self.height))
SDL_SetRenderDrawColor(renderer.handle, self.color.r, self.color.g, self.color.b, self.color.a)
SDL_RenderFillRect(renderer.handle, &rect)
}
}
This is the simplest way I can think of to make graphics/layout code completely platform independent while still optimizing for each rather than falling back to drawing pixel by pixel.
I suppose in this case I can sort of get around the issue by declaring new protocols every time, such as SDLHardwareDrawable
, but that doesn't enforce a standard api and is just boilerplate (and there will be more generic functionality I would have to duplicate each time)
Is there some workaround I could use here? Maybe a different way to express this that I didn't consider?
Either way, wouldn't it make sense for Swift to eventually support generic protocols? It seems like a useful feature.