Thank you for your comments, and I see how unapplied references can be a major concern for this proposal. However, I think that the changes proposed do match the current behavior API author expect. For one, consider a library that manages tasks. The idea is simple: we have a task dictionary and we just add our tasks at the user's command, and then somehow initiate this task (we don't care about the last part):
// However, we have an implicit parent task at ID 0,
// so we’ll implicitly raise the provided tasks' IDs by 1.
addTask(withId id: Int) {
let adjustedId = id + 1 ⚠️
tasks[adjustedId] = …
}
addTask(withId: Int.max) ❌
// Shouldn’t the library handle this case explicitly?
Furthermore, while we explore integer overflows, let's examine some examples with floating points. Imagine a drawing library that has some built-in drawer types, such as rectangles, circles, triangles, etc:
struct Rectangle : Drawer {
let width, height: Double
init(width: Double, height: Double) { ... }
func draw(to canvas: Canvas) {
canvas.addRectangle(width: width, height : height) ⚠️
// Are we sure these are safe.
}
}
let canvas: Canvas = ...
let rectangle = Rectangle(width: .infinity, height: .infinity)
canvas.draw(rectangle) ❌
// Shouldn't the library catch this and handle this explicitly,
// instead of continuing with undefined behavior?
Let's also examine class references, as you also mentioned them in your post. Consider this furniture-related library that allows us to create furniture instances and to propose their estimated price:
class Material {
…
func makeMoreShiny() { … }
}
struct Furniture {
let name: String
let material: Material ⚠️
// This is a reference, and it is not 'protected'
// to ensure that when we create a furniture,
// its material remains constant.
}
let wood: Material = …
// This is a reference.
let plainDesk = Furniture(name: “desk”, material: wood)
var premiumWood = wood
// This is also a reference.
premiumWood.makeMoreShine() ❌
// Now our plain desk has premium wood.
// This obviously not desirable, as creating furniture
// and then seeing it having changed without
// understanding why, is bad API design.
Of course, saying "oh look, the language already has things library authors should be worried about, let's add some more" is a bad approach to evolving a language. However, library authors also need custom features (atomics, unsafe memory management, etc.). Therefore, I think that unapplied function references should remain in the proposal, because not doing so would seem as too much of a limitation. An example of why a library author may benefit from unapplied references is when wanting to provide some behavior through a closure, while also providing an easier-to-use function API:
@propertyWrapper
struct Lowecased {
var rawValue: String
init(wrappedValue: String) { ... }
var wrappedValue: String { ... }
}
protocol Task { ... }
struct EffiecientTask : Task {
func changeName(@Lowercased to newName: String) { ... }
}
func addTask<SomeTask : Task>(_ task: SomeTask) {
nameManager.add(task.changeName)
// Here, 'nameManager' may need to have access to the
// raw value ofthe provided name, which _is_ provided
// by the backing wrapper type.
}
In the above example, should the unapplied reference of changeName(to:)
take a String
or be banned, library authors would not benefit. In the contrary, they would need to separate the function into private and publicly-exposed ones or resort to other workarounds, just to avoid checking that their assertions are valid.
I hope I've addressed your concerns!
Filip