Difficulties to assign default value to a parameter of a function

Viewed 2565

In a class, I defined a private constant, I try to use the constant as a default value of parameter of a function:

class Foo {
  // instance variable
  private let DefaultValue = 10

  // Compiler error: Cannot use instance member 'DefaultValue' as a default parameter
  public func doTask(amount: Int = DefaultValue) {
    ...
  }
}

But I get compiler error: Cannot use instance member 'DefaultValue' as a default parameter.

Then, I also tried declare DefaultValue as private static:

class Foo {
      // static variable
      private static let DefaultValue = 10

      // Compiler error: Static let 'DefaultValue' is private and cannot be referenced from a default argument value
      public func doTask(amount: Int = DefaultValue) {
        ...
      }
    }

But I get new compiler error: Static let 'DefaultValue' is private and cannot be referenced from a default argument value

I need to keep DefaultValue private to this class & I would like to assign default value to the parameter of function with a private variable, whether this is achievable in Swift 4?

4 Answers

I don't think that is possible. The default value is inserted at the calling site, and therefore needs to be public, see also Access control in swift 4.

A possible workaround would be to make the parameter optional, and substitute nil by the default value locally:

class Foo {
    private static let DefaultValue = 10

    public func doTask(amount: Int? = nil) {
        let amount = amount ?? Foo.DefaultValue
        // ...
    }
}

A somewhat complicated, but workable, solution, for hiding the default value you want within your class is to use a protocol and a conforming struct whose intimate details are known only by the file declaring Foo:

// enabling custom stuff
public protocol IntLike {
    var intValue: Int { get }
}

// allowing to pass Int's
extension Int: IntLike {
    public var intValue: Int { return self }
}

public class Foo {
    // the placeholder
    public struct FooIntLike: IntLike {
        // what we really want to hide
        fileprivate let realInt = 10

        public init() { }

        public var intValue: Int = Int.max // or 0, or whatever
    }

    public func doTask(amount: IntLike = FooIntLike()) {
        // default value will expand to a non-nil value for `realInt`
        let amount = (amount as? FooIntLike)?.realInt ?? amount.intValue
        // do your stuff with the amount
    }
}

Callers of doTask are able to pass Int's, while not knowing what the default value provides.

In cases where using nil might not be immediately obvious and the task is either small or easily placed within a private function, just adding another signature works for me.

You could even change it to doTaskWithDefaultAmount() depending on how important it is that the user know about this default value.

class Foo {
    public func doTask() {
        calculateStuff(from: self.defaultValue)
    }
   
    public func doTask(amount: Int) {
        calculateStuff(from: amount)
    }
}

How about a top-level variable?

fileprivate let DefaultValue = 10

class Foo {
    public func doTask(amount: Int = DefaultValue) {
        ...
    }
}
Related