Pitch: private(set) support in Swift Protocol

I'd like to provide a default implementation for Animal.update(_:) and do not like to expose the setter to the outer side.

Is there a way to only allow the extension of Animal(Or the same file of Animal.swift) to access the setter of name?

// Animal.swift
protocol Animal {
    var name: String { get }
    func update(_ newName: String)
}

extension Animal {
    func update(_ newName: String) {
        name = "Animal_\(newName)" // ❌ Cannot assign to property: 'name' is a get-only property
    }
}
// Dog.swift
struct Dog: Animal {
    private(set) var name: String
    mutating func update(_ newName: String) {
        name = "Dog_\(newName)"
    }
}
// Cat.swift
struct Cat: Animal {
    var name: String
}

I'd like to have a private(set) constraint syntax on name. What's your idea on this? Or is there a workaround for such use case?

protocol Animal {
    var name: String { get private(set) }    
    // Or
    private(set) var name: String { get set }
}
2 Likes

In general the ability to spell an internal requirement on a protocol, including such a thing, would be quite welcome — I frequently hit this during framework development where the framework needs some mutation but others will never be able to use it, hiding these using hidden protocols is somewhat suboptimal as it ends up causing casting or representing these methods using underscored names.

I’d support that, from a personal standpoint at least :)

8 Likes

+1. We have to use var _name: String { get set } or add another internal protocol to workaround it for now.

Workaround 1:

public protocol Animal {
    var name: String { get }
    func update(_ newName: String)
    // DO NOT USE THIS
    var _name: String { get set }
}

public extension Animal {
    var name: String { _name }
    func update(_ newName: String) {
        _name = "Animal_\(newName)"
    }
}

Workaround 2:

public protocol Animal {
    var name: String { get }
    mutating func update(_ newName: String)
}
protocol _Animal {
    var name: String { get set }
    mutating func _update(_ newName: String)
}

extension _Animal {
    mutating func _update(_ newName: String) {
        name = "Animal_\(newName)"
    }
}

public struct Cat: Animal {
    public internal(set) var name: String
}
extension Cat: _Animal {
    public mutating func update(_ newName: String) {
        _update(newName)
    }
}

This could be a lot simpler, couldn’t it?

protocol Animal {
  var name: String { get }
  mutating func update(_ newName: String)
}

protocol _Animal: Animal {
  var name: String { get set }
}

extension Animal
where Self: _Animal {
  mutating func update(_ newName: String) {
    name = "Animal_\(newName)"
  }
}

struct Cat: _Animal {
  var name: String
}

IIRC instead of the // DO NOT USE THIS we can prevent it through SPI.

public protocol Animal {
    var name: String { get }
    func update(_ newName: String)
    @_spi(AnimalInternal) var _name: String { get set }
}

public extension Animal {
    var name: String { _name }
    func update(_ newName: String) {
        _name = "Animal_\(newName)"
    }
}
2 Likes

I was doing the same at first. But in real world, Animal will be a public protocol and _Animal is a internal one. The compiler will give an error for the following code.

extension Animal where Self: _Animal {

Of course we can use the same trick as workaround 1 (Just use public protcol _Animal + a comment saying "DO NOT USE THIS externally" or use @_spi mark suggested by @stevapple ).