Skip to content

Instantly share code, notes, and snippets.

@typesanitizer
Last active July 5, 2022 03:07
Show Gist options
  • Save typesanitizer/68cf836ab99429187c3587729ea3224f to your computer and use it in GitHub Desktop.
Save typesanitizer/68cf836ab99429187c3587729ea3224f to your computer and use it in GitHub Desktop.
Varun's Swift and Rust style notes

Varun's notes on Swift and Rust style

Note: This is for my personal projects so I can maintain a consistent style, not for other projects.

Rust code should follow the same guidelines of organization and naming, but the 'MARK:' prefix isn't recognized by editors other than Xcode, so it should be skipped.

Organization

Types and associated methods should be grouped in // MARK: blocks as follows:

// MARK: - TypeName type definition
// ...
// MARK: Helper type definition(s)
// ...
// MARK: Conformance(s)
// ...
// MARK: Construction API
// ...
// MARK: Query API
// ...
// MARK: Modification API
// ...
// MARK: Private API
// ...

A section which doesn't have any entries can be omitted. There should be 1 newline around each // MARK:, except that // MARK: - Type definition should have 2 newlines before it.

  • Type definition: This section should only have 1 type definition, and optionally one or more designated initializers. It shouldn't include any associated types/properties/methods for protocol conformances (trait impls).
  • Helper type definition(s): This section contains types which are mostly "POD types", they're just there to carry some related data together and don't have a lot of functionality. These may be internal or more accessible depending on the situation, they don't need to be fileprivate.
  • Constant(s): Any constant values associated with the type.
  • Conformance(s): Any hand-written conformances.
  • Construction API: Initializers and factory functions.
  • Query API: Code that doesn't modify or consume self.
  • Modification API: Code that modifies self. For classes, code that conceptually modifies the instance should also go here. For Rust, code that consumes, modifies and returns self (e.g. with the Builder pattern) should also go here.
  • Deconstruction API: Code that consumes self. This is less uncommon in Swift, and can be skipped.
  • Private API: API that is fileprivate or private. This may optionally be further subdivided into Constant(s), Construction API, Query API etc.

Reasoning for this breakdown:

  • It makes it clear where the public API starts and ends while looking at the source code. Swift and Rust do not allow writing explicit interfaces (unlike, say OCaml), but often one is interested in knowing the public and private interfaces separately.
  • Knowing which functions are available for construction is useful when trying to create new values for an unfamiliar type. Code completion helps to some extent, but cannot be relied on fully.
    • In Swift, you could have both static methods for constructing new values (these allow you to attach a name to the method which can be useful) and have direct constructors.
    • In Rust, construction functions don't always being with new (sometimes they are prefixed with with or something else).
  • Knowing the full set of methods modify self is useful because you can quickly figure out all the modifications.
  • Grouping functionality makes it clearer where to add new functionality, as well as checking if some functionality exists but by a different name than what you would've guessed.

Organizing types

  • Types should not be nested (using a nested typealias is okay but to be avoided).

    Reason: The presence of nested types (which often deliberately have identical names) makes a plain text search more likely to return false positives.

Naming types

  • Types should have project-unique names. For example, generic names like Context or Options should be avoided, unless there is only a single type with that name.

    Reason: The presence of identically named types in different modules makes a plain text search more likely to return false positives.

Declaration order

  • The order of declarations in a conformance (impl in Rust) should match the order of declarations in the protocol (trait in Rust) definition.
  • The order of methods that are being overriden in a subclass should match the order of declarations in the superclass.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment