sssilver writes: “In my company I find code that’s basically a class with a bunch of static functions in it. When I ask people why not just write the functions directly, they answer that they don’t wanna pollute the namespace. In something like Rust, everything is scoped to the module. What’s the idiomatic way to do this in Swift?”
Swift prefers namespacing to freestanding functions and constants. This applies even in module APIs where you gain a basic level of namespacing for free. You want to keep your namespace clean and focused, scoping items to types and protocols of natural interest.
This practice makes Swift a bit more hierarchical than you might be used to. In Swift prefer to:
- Namespace constants into a type where they naturally fit.
- Namespace functions into the types and protocols they service, either as static methods or by removing the first argument and re-implementing the function as an instance method.
- Add subtypes rather than pollute the global namespace with items that are only of interest within a parent type. Subtypes allow the parent and its nested types to reduce complexity when referring to each other, so
Shape.Triangle
andShape.Circle
see each other asTriangle
andCircle
. - Embed functions that will only be called by a single method client into that method, rather than create a second method that exists only to serve one parent.
- Move operator implementations into the types they service, so you reduce the number of operators implemented in the global namespace.
Where you might have previously created public constants and functions like these, now prefer to incorporate them into existing or utility types:
public let π = CGFloat(Double.pi) // no public let τ = π * 2.0 // no public func halt() { // no PlaygroundPage.current.finishExecution() } extension CGFloat { /// The pi constant, yes public static let (pi, π) = (CGFloat(Double.pi), CGFloat(Double.pi)) /// The tau constant, yes public static let (tau, τ) = (2 * pi, 2 * pi) } extension PlaygroundPage { // This is never going to be beautiful public static func halt() { current.finishExecution() } }
Embedding globals into types as static members provides clean, Swiftier namespacing, with minimal overhead costs. Where possible, prefer extending an Apple-supplied type (like `CGFloat
` and `PlaygroundPage
`) over creating new types (like `MathConstants
` or `PlaygroundRuntimeSupport
`).
At the same time, don’t force constants and functions into classes where they have no natural fit. If you must create a new type to warehouse a namespace, prefer a no-case enumeration, which is guaranteed to be unconstructable.
I’ll probably think of more things after I hit “Publish” on this, so if I missed anything, let me know and I’ll update the post.
Comments are closed.