hamchapman
hamchapman

Reputation: 3341

Default value for optional generic parameter in Swift function

Is it possible to give an optional generic parameter a default value?

I'm trying to do something like this:

func addChannel<T>(name: String, data: T? = nil) -> Channel {

}

let myChannel = addChannel("myChannelName")

But I'm getting an error saying

Argument for generic parameter 'T' could not be inferred

Is it just a case of what I'm trying to do being impossible?

Upvotes: 29

Views: 18462

Answers (5)

Asherah
Asherah

Reputation: 19347

This synthesises a bunch of the other answers here — specifically, I wanted to use a default, while still having a constraint on the type, without adding overloads.

Specifically, my example is passing an optional request body (of generic, Encodable type), and returning a response body (of generic, Decodable type). The result type must be specified.

static func doRequest<Payload: Encodable, Result: Decodable>(
  _ resultType: Result.Type,
  path: String,
  body: Payload? = Optional<Bool>.none,
  method: String = "GET",
) async throws -> Result {

  // ... calculate url ...

  var request = URLRequest(url: url)
  request.httpMethod = method
  if let body {
    request.httpBody = try JSONEncoder().encode(body)
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
  }

  // ... perform request, handle errors ...

  return try JSONDecoder().decode(Result.self, from: data)
}

The key is specifying Optional<Bool>.none as the default. Actually, it doesn't have to be Bool specifically, as long as it's Encodable. (This rules out Optional<Any>.none, but I didn't feel like making up a "DefaultEncodable" just for this.) It doesn't matter, since we don't specify such a value, and we'll only hit the if let body { … } interior if we actually have a proper body to encode anyway.

Upvotes: 0

Math Rules
Math Rules

Reputation: 129

I encountered a similar issue while defining a complex function with a large number of generics, for which overloading would have resulted in an unmanageable number of variations. Here is the code:

struct Channel{
    //Your code here
}

class ChannelManager{
    static let typedNil : Int? = nil //<--- This is what eliminates the error "Argument for generic parameter 'T' could not be inferred"
    
    public static func addChannel<T>(name: String, data: T? = typedNil) -> Channel{
        //Your code here
        return Channel() //To demonstrate that code compiles
    }
}

By declaring typedNil in this manner and using it as a default parameter, the default still is nil as expected, but the compiler sees it as a nil int and so is able to infer the type, avoiding the requirement of adding additional protocol conformance to existing types, or requiring overloads. Note that declaring typedNil as static, otherwise compilation will fail with the error "Cannot use instance member 'typedNil' as a default parameter."

Upvotes: 1

respectTheCode
respectTheCode

Reputation: 43066

I use a combination of the existing answers so that I can both call the function without the optional argument and avoid implementing the function twice.

func addChannel(name: String) -> Channel {
    addChannel(name: name, data: Optional<Any>.nil)
}
func addChannel<T>(name: String, data: T? = nil) -> Channel {
    ...
}

As mentioned above you should avoid Any. In my case I know that T is an Encodable so I use this:

struct DefaultEncodable: Encodable {}

func function1(name: String) {
    addChannel(name: name, data: Optional<DefaultEncodable>.nil)
}
func function1<T: Encodable>(name: String, data: T? = nil) {
    ...
}

Upvotes: 2

shoe
shoe

Reputation: 1070

I ran into the same problem. While not being able to use the generic default traditionally — leaving the argument out completely — I prefer the below to implementing overloads:

let myChannel=addChannel("myChannelName", data: Optional<Any>.none)

Upvotes: 4

Rob Napier
Rob Napier

Reputation: 299275

It's impossible in the way you've done it. Given just the code above, what type is T? The compiler, as it says, can't figure it out (neither can I, and I assume you couldn't either because the data's not there).

The solution to the specific question is to overload rather than use defaults:

func addChannel<T>(name: String, data: T?) -> Channel { ... }

func addChannel(name: String) -> Channel { ... }

let myChannel = addChannel("myChannelName")

But it raises the question of what you're doing here. You would think that Channel should be Channel<T>. Otherwise, what are you doing with data? Without resorting to Any (which you should strongly avoid), it's hard to see how your function can do anything but ignore data.

With Channel<T> you can just use a default, but you'd have to provide the type:

func addChannel<T>(name: String, data: T? = nil) -> Channel<T> { ... }
let myChannel: Channel<Int> = addChannel("myChannelName")

Otherwise the compiler wouldn't know what kind of channel you're making.

(UPDATE ~ Swift 5.2)

Sometimes you'd like a default type for T. You can do that with an overload. For example, you might want the default type to be Never. In that case, you would add an overload like this:

func addChannel<T>(name: String, data: T? = nil) -> Channel<T> { ... }
func addChannel(name: String) -> Channel<Never> { 
    addChannel(name: name, data: Optional<Never>.none)
}

With that, you can have a simpler call:

let myChannel = addChannel(name: "myChannelName") // Channel<Never>

Upvotes: 44

Related Questions