Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement a Swift protocol across structs with conflicting property names

I'm trying to write a protocol that reconciles two different structs that describe the same concept, a stop of some kind. Both have a Code, Description, and Latitude & Longitude coordinates, but for one type, the Description could be nil, and for the other type, the coordinates might be nil.

How can I write a single protocol that reconciles these two structs?

Here's my protocol:

protocol Stop {
    var Code : String { get }
    var Description : String { get }
    var Latitude : Double { get }
    var Longitude : Double { get }
}

And the two types of stops:

struct BusStop : Stop {  // Compiler error: doesn't implement Description
    var Code : String
    var Description : String?
    var Latitude : Double
    var Longitude : Double
    // Various other properties
}

struct TrainStop : Stop {  // Compiler error: doesn't implement Latitude or Longitude
    var Code : String
    var Description : String
    var Latitude : Double?
    var Longitude : Double?
    // Various other properties
}

In C# (my mother tongue), I would write an explicit interface implementation like so (pseudo-code):

// At the end of the BusStop struct
var Stop.Description : String { return Description ?? string.Empty }

// At the end of the TrainStop struct
var Stop.Latitude : Double { return Latitude ?? 0 }
var Stop.Longitude : Double { return Longitude ?? 0 }

However, I'm not aware of any similar functionality in Swift. Given that I'm unable to change the existing property definitions of BusStop and TrainStop, how can I write the Stop protocol so that it wraps around both structs and returns the properties when available?

like image 501
Extragorey Avatar asked Sep 17 '25 07:09

Extragorey


1 Answers

The desired feature from explicit interface implementations is that they are statically dispatched, right? If you use Description on a BusStop, it will be an optional string, but if you use Description on a Stop, it will be a non-optional string.

In Swift, extension members are statically dispatched, so you can make use of this to achieve something similar:

extension Stop where Self == BusStop {
    // Since the type of "self" here is BusStop, "Description" refers to the one declared in BusStop
    // not this one here, so this won't cause infinite recursion
    var Description : String { return self.Description ?? "" }
}

extension Stop where Self == TrainStop {
    var Latitude: Double { return self.Latitude ?? 0 }
    var Longitude: Double { return self.Longitude ?? 0 }
}

This code shows that this works:

let busStop = BusStop(Code: "ABC", Description: "My Bus Stop", Latitude: 0, Longitude: 0)
print(type(of: busStop.Description)) // Optional<String>
let stop: Stop = busStop
print(type(of: stop.Description)) // String

However, I still don't think this is good Swift code. It is often bad to just directly translate an API from one language to another. If I were you, I would make Longitude, Latitude and Description in Stop to be all optionals.

like image 56
Sweeper Avatar answered Sep 19 '25 14:09

Sweeper