# Is there a way to deprecate public APIs for library clients only?

**URL:** <https://forums.swift.org/t/is-there-a-way-to-deprecate-public-apis-for-library-clients-only/52369>\
**Category:** Using Swift\
**Tags:** deprecation, availability\
**Created:** [September 26, 2021, 8:12pm UTC](https://forums.swift.org/t/is-there-a-way-to-deprecate-public-apis-for-library-clients-only/52369 "2021-09-26T20:12:31Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![Karl](https://avatars.discourse-cdn.com/v4/letter/k/34f0e0/32.png) [@Karl](https://forums.swift.org/u/Karl)\
**Post date:** [September 26, 2021, 8:12pm UTC](https://forums.swift.org/t/is-there-a-way-to-deprecate-public-apis-for-library-clients-only/52369/1 "2021-09-26T20:12:32Z")

</div>

So, I have a library, which includes a public API for percent-encoding. The library comes with a bunch of "encode sets" (sets of characters which should be percent-encoded) as defined in the URL standard, and it is possible for users for create their own by conforming to a protocol.

The API looks like this:

```swift
extension StringProtocol {

  public func percentEncoded<EncodeSet: PercentEncodeSet>(
    as encodeSet: EncodeSet
  ) -> String {
    // ...
  }

  public func percentEncoded<EncodeSet: PercentEncodeSet>(
    as encodeSet: KeyPath<URLEncodeSet, EncodeSet>
  ) -> String {
    percentEncoded(as: URLEncodeSet()[keyPath: encodeSet])
  }
}

public struct URLEncodeSet {
  public var userInfo: UserInfo { ... }
}

```

Now, this might look a bit strange at first - I have one API which accepts an instance of a `PercentEncodeSet`, and another which accepts a KeyPath on an empty struct which serves as a namespace. The benefit of this is that it allows a more fluid API for well-known encode sets:

```swift
someString.percentEncoded(as: \.userInfo)

```

Without requiring the user to instantiate an object or write out type names such as `URLEncodeSet.UserInfo.self`.

In the mean time, [SE-0299: Extending Static Member Lookup in Generic Contexts](https://github.com/apple/swift-evolution/blob/main/proposals/0299-extend-generic-static-member-lookup.md) happened. The API I have is exactly the kind of "static member on protocol" API which the proposal intends to make easier. So instead of all of this awkward KeyPath stuff: I can simply write an extension on the protocol:

```swift
extension PercentEncodeSet where Self == URLEncodeSet.UserInfo {
  public static var userInfo: URLEncodeSet.UserInfo { ... }
}

```

And users can write:

```swift
someString.percentEncoded(as: .userInfo)

```

Without having to go through the KeyPath overload. Very cool!

Unfortuntately, this new syntax requires a Swift 5.5 compiler, and I'd like to support Swift 5.3+. So what I'd like to do is to include _both_, but steer users with a Swift 5.5 compiler towards the new syntax. It turns out that it is actually possible:

1. Enclose the above `PercentEncodeSet where Self == X` extensions in a `#if swift(>=5.5)` block.

2. Use `@available` to mark the KeyPath overloads as deprecated for users with Swift 5.5+ compiler:

But what happens is that now _I'm_ getting a bunch of deprecation warnings when compiling with Swift 5.5! Not cool! I'd still like to use the KeyPath version internally, because I still need the ability to build with Swift 5.3, but I'd like _clients_ to use the more modern syntax.

**So this is my question** : Is there a way to say "this API is deprecated for clients using the latest Swift compilers, but not for me, because I still need to work with older compilers"?

---

<div class="post-metadata">

**Author:** ![SDGGiesbrecht](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/sdggiesbrecht/32/4768_2.png) [@SDGGiesbrecht](https://forums.swift.org/u/SDGGiesbrecht)\
**Post date:** [September 26, 2021, 10:43pm UTC](https://forums.swift.org/t/is-there-a-way-to-deprecate-public-apis-for-library-clients-only/52369/2 "2021-09-26T22:43:01Z")

</div>

Obviously an actual attribute or other feature would be superior, but in the meantime the best I can think of would be to make the implementation method internal and undeprecated (and differentiated by e.g. an underscore), while having an equivalent (not underscored) forwarding public symbol marked deprecated, but make sure any internal call sites use the internal variant and not the public API.

---

<div class="post-metadata">

**Author:** ![Karl](https://avatars.discourse-cdn.com/v4/letter/k/34f0e0/32.png) [@Karl](https://forums.swift.org/u/Karl)\
**Post date:** [September 27, 2021, 12:24am UTC](https://forums.swift.org/t/is-there-a-way-to-deprecate-public-apis-for-library-clients-only/52369/3 "2021-09-27T00:24:51Z")

</div>

So after working though this for a bit, I realised a couple of things:

1. What I really want is to deprecate this API based on the client's minimum supported Swift language version.

2. As it happens, package declarations include a `swiftLanguageVersions` property, which gets passed to the compiler. For some reason, this is an array rather than a minimum version, but whatever.

3. I had forgotten to set this, and since I want my library to only use 5.3 language features (unless I opt-in to a newer version via `#if swift`), I set it to `[.version("5.3")]`.

4. `swift build` then correctly passes this to the compiler as `-swift-version 5.3`. Xcode doesn't, and passes `-swift-version 5` instead. Because Xcode.

5. Actually it doesn't even matter, because the swift compiler doesn't have a language mode 5.3:

The swift language mode properties are described by:

- [SE-0151 Package Manager Swift Language Compatibility Version](https://github.com/apple/swift-evolution/blob/master/proposals/0151-package-manager-swift-language-compatibility-version.md), and
- [SE-0209 Package Manager Swift Language Version API Update](https://github.com/apple/swift-evolution/blob/main/proposals/0209-package-manager-swift-lang-version-update.md)

They're very short and, to be honest, not _entirely_ helpful (it's pretty much "support the compiler's `-swift-version` argument", without a clear description of what that means). But there are hints that it was intended to allow what I'm trying to do:

> This is important for packages which want to add support for a newer Swift language version but also want to retain compatibility with an older language and tools version [SE-0209]

> [@SE-0209 Package Manager Swift Language Version API Update](https://forums.swift.org/t/se-0209-package-manager-swift-language-version-api-update/11815/13):
>
> Our [original support for declaring a Swift language version](https://github.com/apple/swift-evolution/blob/master/proposals/0151-package-manager-swift-language-compatibility-version.md) gave package authors the ability to have dual-language-mode packages. This allows you to have a package that can adopt newer Swift features will still being usable by clients on the older version of the tools. In many cases your package may have to conditionalize some code on the Swift language version the code is being compiled with in order to support being used with either language version, but I expect that many package authors are willing to do that to avoid leaving older clients behind.

So ultimately the conclusion I'm coming to is that SwiftPM is able to express what I want. I think the problem is that _the compiler_ only knows about major Swift language versions (v4 vs v5, but not v5.3 vs v5.5). That means I can't deprecate something in a new minor version of the language (5.5) and keep compiling using an older minor version (5.3). All I can tell it is "build in v5 mode".

This is kind of problem. Swift 5.0 was released in March 2019, and that every feature since then has come in a minor language version. And once Swift 6 does come, all we'll be able to say is "build in v5 mode", without saying whether we mean 5.1, 5.3, 5.5, etc.

---

<div class="post-metadata">

**Author:** ![Karl](https://avatars.discourse-cdn.com/v4/letter/k/34f0e0/32.png) [@Karl](https://forums.swift.org/u/Karl)\
**Post date:** [September 27, 2021, 12:29am UTC](https://forums.swift.org/t/is-there-a-way-to-deprecate-public-apis-for-library-clients-only/52369/4 "2021-09-27T00:29:59Z")

</div>

Right, but then there's a problem if the client is another application/library in the same situation: building with a 5.5 compiler, but needing 5.3 compatibility. The problem I was having with the library is just a symptom of a larger problem.

We need to be able to tell the compiler _precisely_ which version of the language we're targeting.
