Organizing swift source code?

As a new developer to swift, I'm looking to get more information on best practices for organizing large projects. I’ve read quite a bit about modules and element nesting, and I understand that “namespaces/submodules” is a bit of a controversial topic / still being worked out, but I’ve never reached a satisfying answer as to how to approach this.

Right now, in 2018, what is the best practice for organizing code? Suppose my domain looks like the following:

I have a top level domain called “Foo”. In this domain, I plan to have large libraries of components that other people can use called “Core”, “Widgets”, “Maguffins”, “Transforms” and “Utilities”. Inside of each of these libraries there will be multiple classes provided that could be described in terms of subdomains (or subsubdomains). For example, “Core” might have a “Maths” subdomain and a “Scaffolding” subdomain, and the “Maths” subdomain might have subdomain of its own like “Trig” and “Calculus”.

I realize this example is a bit contrived but I find myself wondering how to structure code within an area that has deep domains / will end up with large segments of code.

Reading what I can find about SPM, it seems like I’m required to at least have a top level folder in the “Sources” directory, and then inside that I’m allowed to have a number of module subdirectories (?):

Sources
| - Foo
    | - Core
    | - Widgets
    | - Maguffins
Packages.swift

While this will work for shallow projects, I’m unsure how this will scale up. Am I allowed to have subfolders underneath the “module” subfolders (e.g. “Maths” under “Core”)? Do I need to plan to break this across library boundaries; i.e. should I have a “FooCore” and a “FooWidgets” library? How does namespacing work there?

I’ve also seen the things about enum based “namespacing” but the asymmetry of needing to write “enum Foo” in one file and “extension Foo” in others seems hard to follow. I suppose in my case that the “enum” version could go in the “Core” library and everything else could follow suit. Is this considered good / bad practice?

Any guidance folks can provide would be greatly appreciated!

Start with SPM is very cool. Because you will learn about Dependency hall, and this very right step to understand how modules work.

sadly, this is largely an unsolved problem in swift. the best thing you can do at this time is to have all your code in one module, but split into multiple directories in your source folder. you’ll want to keep the number of top level declarations you have as low as possible to avoid name conflicts but you should already be doing that anyway. if at a later point Swift modules become viable, it will not be too hard to spin them off into resilient modules.

Herm, that more or less confirms what I thought might be the case. I'ts really a shame too as it adds a fairly large tick back in the "don't use swift" column for the project I'm planning. I'll be managing a large team of developers working on the next generation of a complex robotics library with lots of different capabilities and domains, so it's pretty critical that we have a clean, consistent way of organizing code.

I read your Namespaces x submodules post from the beginning to try to get a handle on what the "current state of the art" is here. Did that ever resolve to anything elsewhere? It seems like solving this is a pretty critical aspect for swift to get any traction in the systems code space :thinking:

Do you have an opinion on the enum approach? Does anyone actually do that? I've tried to look at existing large-ish code bases like Vapor; it appears everyone just tries to carefully name everything to avoid namespace polluting and that sounds like the path to madness to me.

IMO it’s not nearly as bad in practice. I work in payments and while we could have modules for each payment type and symbols like PeerToPeer.Payment or Petrol.Payment, having P2PPayment and PetrolPayment instead does not feel like a problem. Certainly it’s not a thing I would prioritize when evaluating Swift for a project.

I put my code in enums since I like the readability of P2P.Payment and Petrol.Payment. I might do the reverse for this example though since Payment is the abstract concept and therefore might be protocol or base at some point, I.e. Payment.P2P and Payment.Petrol.

However as someone already said, hardly a show stopper either way.

2 Likes

Unfortunately, there's an awful bug within Swift related to enum namespacing:
https://bugs.swift.org/browse/SR-631

This bug is basically unfixable if you're using the SPM (it can be fixed in XCode by rearranging the file compilation order, apparently).

IMHO, and this may a bit harsh, Swift is not ready for this kind of scenario yet. I'm working on a somewhat large-ish domain with Swift, and tbh, I wish we used a different language because the language just isn't mature enough (my most serious gripe is with the state of the testing tools).

1 Like

The biggest issue currently is the fact that modules are also resilience boundaries (although that might not always be the case); if you design a project by separating it into modules, you need to be prepared for a lot of @inlinable annotations to recover performance. That's more of an issue for things like maths libraries (where there are a number of small functions or types that are frequently called) than it is for larger frameworks (where you're often just calling a few methods that do large amounts of work).

Besides that: within the package manager, you can specify multiple folders per module; if the Sources folder for a particular module contains subfolders, all of the sources in those subfolders will be included in the parent module and will be within the same namespace. The hierarchy of those folders is displayed within Xcode as you'd expect.

With regards to namespacing: I personally haven't found it to be an issue within my projects. If you have multiple types with the same name but different purposes as public API they probably are conceptually separated enough that they should be in different modules. I agree that this isn't a major issue in practice, and certainly not something that can't be worked around. While less than ideal, languages like C or Objective-C managed with just one global namespace – it's definitely possible to build large projects that way.

It's also worth noting that you can use @_exported import SomeModule to combine multiple modules into a single public interface: @_exported means that any module that imports the current module also imports the exported modules.

As an example, for my game engine, the root modules are roughly:

  • Maths
  • Utilities
  • Rendering API
  • Rendering Backends
  • Engine
  • IO
  • Debug drawing tools
  • Gameplay
  • Editor
  • Target Applications
  • ... and a number of C library dependencies.

Within something like Engine, there's a number of subfolders; however, there shouldn't be any duplication of concepts, so there aren't namespace collisions. If I have multiple Mesh classes, for example, that probably means they need a more descriptive name than Mesh or that I need to refactor.

1 Like

agreed. Also, the . is your best friend in large projects. Nest your types aggressively.

1 Like