Best practices for cross-import overlays declaring `CustomStringConvertible` conformances

i have modules that import a string formatting module in order to implement CustomStringConvertible.description.

// module: CRC
import Base16

extension CRC32:CustomStringConvertible 
{
    @inlinable public 
    var description:String 
    {
        ...
    }
}

to remove this dependency, i would like to factor out the CustomStringConvertible into an underscored overlay module:

// module: _CRC_Base16
@_exported import CRC
import Base16

extension CRC32:CustomStringConvertible 
{
    @inlinable public 
    var description:String 
    {
        ...
    }
}

but this would make string descriptions behave differently depending on whether you do:

import _CRC_Base16

let crc32:CRC32
print("\(crc32)")

or

import CRC

let crc32:CRC32
print("\(crc32)")

what are best practices for dealing with this situation?

I don’t think you can. Because conformances to CustomStringConvertible are looked up dynamically, the compiler cannot tell from the client code whether they will be needed, and so either you have to put them in the base module, or put them in a second module that’s explicitly imported. Cross-import overlays are a form of conditional import, but the only condition they support is “did you import this other module too”, and even if they were more general than that, “will this type ever be dynamically cast to any CustomStringConvertible?” is something that even the optimizer has trouble answering.

(This applies to any protocol used with as?.)

i’m well-aware that the compiler will not automatically import modules just to obtain conformances, this is more a question about best practices when vending packages that split up conformances into multiple modules.

because usually when a conformance is missing that just results in a compile error, but some protocols (RandomAccessCollection:Collection, CustomStringConvertible, etc.) cause more subtle degradations when conformances are missing.

1 Like