Discussion: are these antipatterns really antipatterns?

long ago (2016?) i internalized writing swift in certain ways for reasons that made sense in the early days of the language when the compiler was less mature.

  1. never pass a collection as inout:
extension Model 
{
    // bad? 
    func populate(_ dictionary:inout [Int: Int])
    {
    }
}
  1. a (nonmutating) member of the big struct should always take the little struct as a parameter, never the other way around:
extension Int 
{
    // bad? 
    func compute(_ model:Model) -> Int
    {
    }
}
  1. Sequence.lazy escapes the closure, and @escaping is radioactive, so it should not be used:
// bad? 
let set:Set<Int> 
let dictionary:[Int: Int] = .init(uniqueKeysWithValues: 
    try set.lazy.map { ($0, try transform($0) } )

are/were these antipatterns really antipatterns?

#1 seems like superstition to me, but maybe it comes from the days before proper exclusivity checking, when it was possible to re-entrantly use a value and potentially get the wrong result.

Non-self parameters used to always be passed as “owned”, so #2 was a valid low-level optimization to avoid a struct copy. These days most parameters are passed as “borrowed” (the exceptions being init parameters and setter values), so it doesn’t matter so much. The compiler is also much better at avoiding unnecessary copies within a function or across functions in the same module (or those declared inlinable). Some day we’ll get explicit control over owned vs borrowed parameters too.

#3 matters a little if you’re using closures that you get as parameters, but you can get around it with withoutActuallyEscaping, so I don’t think there’s actually any reason to follow this one.

8 Likes

thanks!!

right. i’m gradually unlearning many things i cargo cult-ed from the swift 3.x days.

great! i use map most often with simple keypath property accessors, so external closure parameters aren’t a common situation.