Throwing properties allow defining computed properties that throw an error on failure. SE-310 introduced this feature in Swift 5.5 and is part of the async-await concurrency changes allowing async properties to throw errors.
By defining throwing computed properties, we better handle unhappy flows without defining methods for simple accessors—the same counts for custom subscripts that can be defined as throwing too.
Defining throwing properties in Swift
A throwing property can be defined by configuring the get statement with a throws
keyword:
struct SampleFile {
let url: URL
var data: Data {
get throws {
try Data(contentsOf: url)
}
}
}
In this example code, we defined a wrapper around a sample file for our tests. A computed property exists as an accessor to data representing the underlying sample file. As the Data
initializer can throw an error, we benefit from defining our computed property as throwing.
Without this new functionality, our sample file structure would likely look as follows:
struct SampleFile {
let url: URL
func data() throws {
try Data(contentsOf: url)
}
}
Without the possibility of defining throwing computed properties, we have to define a method instead of throwing an error on failure. Using a computed property makes more sense as we only have a single executing method to access a modified version of our sample file URL. Therefore, in this case, using a throwing computed property is a much cleaner solution.
Defining a throwing custom subscript
We can define custom subscripts as throwing too. In the following example code, we’re extending the ImageCache
example from my article around custom subscripts to allow accessing an image for a URL through a subscript:
extension ImageCache {
subscript(_ url: URL) -> UIImage {
get throws {
guard let image = imageStore[url] else {
throw Error.imageNotFound
}
return image
}
}
}
We decided to throw an error whenever an image is requested from the cache that no longer exists. Whenever the image isn’t found, we throw our own custom-defined imageNotFound
error.
Defining a throwing getter for stored properties
Unfortunately, it’s not possible to define a throwing getter in combination with a setter. Doing so would result in the following error:
‘set’ accessor is not allowed on property with ‘get’ accessor that is ‘async’ or ‘throws’
This limitation exists to make the implementation as simple as possible for now. Quoted from the proposal:
The main purpose of imposing this read-only restriction is to limit the scope of this proposal to a simple, useful, and easy-to-understand feature. Limiting effects specifiers to read-only properties and subscripts in this proposal does not prevent future proposals from offering them for mutable members.
In other words, it’s not unlikely to see a future Swift update introducing the same functionality for mutable members too. For now, however, we have to deal with the restriction of using them with computed properties only.
Deciding between a throwing property vs. a throwing method
When defining throwing properties in Swift, it’s good to evaluate whether it’s the right decision to use a computed property over a method definition. There’s no strict answer to this, and it’s mostly a case per case decision. In my experience, developers decide to go for a method once the computation becomes more complicated. A method tells implementations “computation will happen,” whereas properties might hide the underlying side effect of heavy computations.
Once again, you’re not restricted to make complex computed properties, but always consider readability and predictable code for your fellow developers on a project.
Conclusion
Throwing properties in Swift is a great tool for catching unhappy flows without defining methods for simple accessors. We can apply the same technique for custom subscripts, allowing most of our code to be throwable. Being careful to pick between throwing properties and methods makes sure our code is predictable as possible.
If you like to improve your Swift knowledge even more, check out the Swift category page. Feel free to contact me or tweet to me on Twitter if you have any additional tips or feedback.
Thanks!