Function returning a generic with different type parameter

Hi, may I ask why the following code can't compile?

struct Test<X> {
    var value: X
}

extension Test {
    func create<Y>() -> Test<Y> {
        let value = "\(self.value)"
        return Test(value: value)
    }
}

What I want to achieve is to create a new struct of generic type Test with a different type parameter. It's quite a reasonable operation to me, but the compiler seems to get confused and gives error message:

Cannot convert return expression of type 'Test<X>' to return type 'Test<Y>'

I wonder if this is a bug, a feature, or a limitation in the compiler?

UPDATE:

As pointed out by Karl and others, I gave a incorrect example. The correct example to demonstrate the "issue" should be:

struct Test<X> {
    var value: X
}

extension Test {
    func convert<Y>(_ converter: (X) -> Y) -> Test<Y> {
        let value = converter(self.value)
        return Test(value: value)
    }
}

@ensan-hcl's solution: use Test<Y>

@anon9791410's solution: use .init()

Thanks.

1 Like

Error message seems strange, but what you are doing is returning Test<String>.

// it compiles.

struct Test<X> {
    var value: X
}

extension Test {
    func create() -> Test<String> {
        let value = "\(self.value)"   // this is value of type String
        return Test<String>(value: value)
    }
}

In function declaration, type parameters are decided by the user, not function itself.

1 Like

Ensan, thanks for your reply. But my goal is to make the create() function a generic function. For example, the create() function may take a closure to do the actual conversion and hence the type parameter of the output value should depend on the closure passed by caller.

1 Like

It's still generic (using the term loosely) insofar as you can call the function on any Test<X>. But you're returning a Test<String> no matter what. Not sure what exactly you're after.

@ensan-hcl I don't know why the compiler forces you to specify <String> in the return statement. :thinking: I feel like it should be able to infer that <X> = <String> based on the initializer argument being a string.

3 Likes
extension Test {
    func create<Y>(_ converter: (X) -> Y) -> Test<Y> {
        let value = converter(self.value)   // this is value of type Y
        return Test<Y>(value: value)
    }
}

It compiles. How about it?


I too think it's odd.

3 Likes

I don’t think the example is correct, but I think I know which behaviour you’re describing: using the type name Test within an extension of Test assumes the same generic parameters, even when normal type inference should be able to tell that it isn’t what you meant.

I’ve encountered it a couple of times; it’s a bit annoying. Fortunately, the answer is usually simple: just be explicit about the type you mean.

Ah, @ensan-hcl beat me to it :slight_smile:

6 Likes

Thanks! I would never figure it out myself. It never occurred to me that I should use Test<Y> explicitly.

1 Like

Yes and sorry for the bad example. The actual code I work on uses closure. I tried to give a simple example but didn't realize I gave a wrong one.

You should not. .init is the way to suck up the info from the return type in a DRY fashion. :cocktail:

extension Test {
  func create<Y>() -> Test<Y> {
    let value = "\(self.value)" as! Y
    return .init(value: value)
  }
}

(Test(value: "🤷‍♂️").create() as Test<String>).value // "🤷‍♂️"
4 Likes

why do you need to define another generic Y, you can use X directly.

struct Test<X> {
    var value: X
}

extension Test {
    func create() -> Test where X == String  {
        let value = "\(self.value)"
        return Test(value: value)
    }
  
    func create1(param: X) -> Test {
        return Test(value: param)
    }
  
    func create2<Y>(_ converter: (X) -> Y) -> Test<Y> {
        let value = converter(self.value)   // this is value of type Y
        return Test<Y>(value: value)
    }
  
    func create3<Y>(param: Y, _ converter: (Y) -> X) -> Test<X> {
        let value = converter(param)
        return Test<X>(value: value)
    }
}
1 Like

Hi @anon9791410, thanks for the solution. It works indeed. May I ask why .init(value: value) works but Test(value: value) doesn't? What's the difference here? Do you know if there is any document explaining this usage?


@mxy, I just updated the example in my original question. What I really want to implement is your create2(). I think an additional type parameter Y is need, right?


Thanks all. Maybe I haven't tried hard enough, but using generics in Swift always reminder me of Perl's motto: "there is more than one way to do it". Did I say I like Python much more? :sweat_smile:

I found this report and comment by @jrose. Maybe this report says the same thing.

the rule (which I think we regret now) is that within a type context the unadorned name of the type refers to the current Self type, rather than a generic type with inferred parameters. You'll have to explicitly say Set<String>(arr) here.

Therefore, about this 'issue', it would not be fixed.

5 Likes

Thanks!

1 Like