Property wrappers that throw?

Swift currently allows one to make a property wrapper that can throw:

@propertyWrapper
struct Foo {
    var wrappedValue: String
    enum FooError: Error {
        case foo
    }
    init(wrappedValue x: String) throws {
        // try parsing from the wrapped value
        throw FooError.foo
    }
}

But whenever I try to use such a property wrapper, I get a compiler error:

struct Bar {
    @Foo var bar: String   // Error: call can throw, but errors cannot
                           //  be thrown out of a property initializer
    init(bar: Int?) throws {
        try self.bar = bar
    }
}

What am I doing wrong? Or are throwing property wrapper inits simply not feasible in current Swift?

The only workarounds currently that I'm aware of is to catch any errors that might be thrown inside the init method of the property wrapper, and then either call fatalError() or use some default value for the wrapped value, neither of which seem very ideal...

Currently, a read-only computed property's accessor can throw, not a property wrapper's accessor or initializer. So, you would see an error like this:

@propertyWrapper
struct Abstraction<T> {
    private var value : T
    
    init(_ initial : T) { self.value = initial }

    var wrappedValue : T {
         // error: property wrappers currently cannot define an 'async' or 'throws' accessor
        get throws { return value } 
    }
}

During the review of SE-310, there was a desire to expand coverage of effectful properties to include a property wrapper's accessors and settable computed properties. I think it's feasible to do it at some point. I'm not sure if effects make sense on a property wrapper's initializer, though.

In my case, the property wrapper is wrapping a property whose creation involves calling a throwing function. It should never actually throw at runtime if everything is setup properly, so I don't want to resort to making the property optional and just returning nil if it fails. However if the initialization does fail, I would prefer to have the tools to handle that situation gracefully, rather than being forced to resort to fatalError() or returning a default value.

Maybe someday this will get addressed

If it's really a misconfiguration that shouldn't ever happen with a correctly-written client, fatalError is honestly the more graceful way to fail, rather than forcing clients to deal with a fake source of dynamic failure.

The problem with fatalError is that if you are writing integration tests, how do you write a test that expects this to happen and still passes?

Some testing frameworks do actually have the ability to test for a crash, but yes, I agree that testing precondition checks can be annoying.

I'm familiar with one such framework, ErrorAssertions.

1 Like
Terms of Service

Privacy Policy

Cookie Policy