Generic type does not recognize the passed-in parameter even though it satisfies the generic requirements

I am trying to implement something internal and expose its functionality with a generic wrapper:

Public protocol that promises the functionality:

public protocol FooProtocol {
    associatedtype ValueType
    var value: ValueType { get }
}

Internal concrete type that provides the implementation:

internal struct NumericFoo<NumericType: Numeric>: FooProtocol {
    let value: NumericType
}

Public wrapper type that expose the functionality:

public struct Bar<FooType: FooProtocol> {
    private let underlying: FooType

    internal init(foo: FooType) {
        self.underlying = foo
    }
}

extension Bar: FooProtocol {
    public var value: FooType.ValueType { return self.underlying.value }
}

Public initializer in the wrapper type that forward the initialization to the internal type:

public extension Bar {
    init<NumericType: Numeric>(_ number: NumericType) {
        let foo = NumericFoo(value: number)
        self.init(foo: foo) /// Error here
/// Foo.swift:35:14: error: cannot invoke 'Bar<FooType>.init' with an argument list of type '(foo: NumericFoo<NumericType>)'
///         self.init(foo: foo)
///              ^
/// Foo.swift:35:14: note: expected an argument list of type '(foo: FooType)'
///         self.init(foo: foo)
///              ^
    }
}

Why can't the compiler interpret the NumericFoo<NumericType> as FooType? Surely NumericFoo<NumericType> conforms to FooProtocol, which satisfies the condition FooType: FooProtocol in the declaration of struct Bar?

You're extending Bar without any constraint. So self.init takes FooType without any constraint. You'll need to constrain FooType to match NumericFoo<NumericType>, but NumericType is internal, and generic, so you can't do

public extension Bar where FooType == NumericFoo<NumericType> {
  ...
}

because, as said, NumericFoo is internal, and we don't (yet) support generic extension and you need generic NumericType to work with.

Are you sure you need a protocol with associatedType? It makes the whole thing needlessly complex for something that could be easily done with opaque result type.

public protocol FooDouble {
  var value: Double { get }
}

// internal is implied
struct Bar: FooDouble {
  ...
  var value: Double { ... }
}

// vent out the `FooDouble` using `Bar`
public func getSomeFoo() -> some FooDouble {
  return Bar(...)
}

If you really must have associated type, the current setup won't work since the Bar you vented out will have NumericFoo interacted in type information, which is not allowed since NumericFoo is not public. You'll need to use NumericFoo as implementation detail.

public struct Bar<NumericType: Numeric> {
    let underlying: NumericFoo<NumericType>
}

extension Bar: FooProtocol {
    public var value: NumericType { return self.underlying.value }
}

public extension Bar {
    init(_ number: NumericType) {
        self.init(underlying: NumericFoo(value: number))
    }
}

What exactly do you plan on doing? What is public/internal in the scheme you're trying to achieve?

It seems FooProtocol and Bar are public, which is really odd. Generally in this scenario you want FooProtocol to be public, and everything else to be non-public.

Actually you cannot do this even if NumericFoo is public because NumericFoo would be an undeclared type in this case.

But Why?

For Example, I understand that the following is wrong:

func fp<T: P>() -> T {
    ....
}

let t = fp()

Because the type of the t cannot be inferred.

However in this case, the type of Bar.FooType is settled in this case, and it is NumericFoo<NumericType>. So if we pass a Double in that initializer:

public extension Bar {
    init<NumericType: Numeric>(_ number: NumericType) {
        let foo = NumericFoo(value: number) // <== The type of the `foo` will *NOT* be generic but a specific one: NumericFoo<Double>
        self.init(foo: foo) // <== This will be like `self.init(foo: FooType) where FooType == NumericFoo<Double>`
    }
}

And because of that, the following function will work and you can try:

func test<N: Numeric>(_ n: N) {
    let foo = NumericFoo(value: n)
    let bar = Bar(foo: foo)
    print(bar.value)
}

test(Double.pi) // print 3.141592653589793

Yes, I do need the associatedtype to keep the wrapped value for later use.

What I am trying to do is to provide an API that deals with different kind of types, and is able to refer to its original value.

public protocol FooProtocol {
    associatedtype ValueType
    var value: ValueType { get }
    func doSomething()
}

And internally, we provides implementations for all kinds of types we support:

internal struct NumericFoo<T: Numeric>: FooProtocol {
    let value: T
    func doSomething() { ... }
}

internal struct StringFoo<T: StringProtocol>: FooProtocol {
    let value: T
    func doSomething() { ... }
}

internal struct NumericRawFoo<T: RawRepresentable>: FooProtocol where T.RawValue: Numeric {
    let value: T
    func doSomething() { ... }
}

internal struct StringRawFoo<T: RawRepresentable>: FooProtocol where T.RawValue: StringProtocol {
    let value: T
    func doSomething() { ... }
}

internal struct NumericCollectionFoo<T: Collection>: FooProtocol where T.Element: Numeric {
    let value: T
    func doSomething() { ... }
}

internal struct StringCollectionFoo<T: Collection>: FooProtocol where T.Element: StringProtocol {
    let value: T
    func doSomething() { ... }
}

/// and more...

And for the external use, a wrapper type is provided with different initializers:

public struct Bar<FooType: FooProtocol>: FooProtocol {
    private let underlying: FooType

    internal init(foo: FooType) {
        self.underlying = foo
    }

    public var value: FooType.ValueType { return self.underlying.value }
    public func doSomething() { self.underlying.doSomething() }
}

And initializers for different type:

public extension Bar {
    init<T: Numeric>(_ value: T) {
        let foo = NumericFoo(value: value)
        self.init(foo: foo)
    }

    init<T: StringProtocol>(_ value: T) {
        let foo = StringFoo(value: value)
        self.init(foo: foo)
    }

    init<T: RawRepresentable>(_ value: T) where T.RawValue: Numeric {
        let foo = NumericRawFoo(value: value)
        self.init(foo: foo)
    }

    init<T: RawRepresentable>(_ value: T) where T.RawValue: StringProtocol {
        let foo = StringRawFoo(value: value)
        self.init(foo: foo)
    }

    init<T: Collection>(_ value: T) where T.Element: Numeric {
        let foo = NumericCollectionFoo(value: value)
        self.init(foo: foo)
    }

    init<T: StringProtocol>(_ value: T) where T.Element: StringProtocol {
        let foo = StringCollectionFoo(value: value)
        self.init(foo: foo)
    }

/// and more...
}

Sorry I misunderstood you.

I make the NumericFoo public and add the proper constraints and it works:

public struct NumericFoo<NumericType: Numeric>: FooProtocol {
    public let value: NumericType
}

public struct Bar<FooType: FooProtocol, ValueType> where FooType.ValueType == ValueType {
    private let underlying: FooType

    internal init(foo: FooType) {
        self.underlying = foo
    }
}

extension Bar: FooProtocol {
    public var value: ValueType { return self.underlying.value }
}

public extension Bar where ValueType: Numeric, FooType == NumericFoo<ValueType> {
    init(_ value: ValueType) {
        let foo = NumericFoo(value: value)
        self.init(foo: foo)
    }
}

Thanks for helping out!

Notice also that Bar have the same access level as Bar.FooType. So if Bar was just a thin wrapper around FooProtocol, it's probably a moot point now. The rest of Bar functionality can also be in extension FooProtocol { ... }.

Terms of Service

Privacy Policy

Cookie Policy