Is there any news on whether or not this will ever be implemented?
Throwing properties and subscripts would we a really neat addition.
This would also allow adding dynamic throwing properties when using it along dynamic member lookup from SE-0195 which would be nice for Python interop.
I would also like to see this. It might also apply to both get and set if they are both supplied:
subscript(idx: Int) throws -> Element {
get {...} //Get can throw
set {...} //Set can throw
}
How about, if you want throws for computed properties, you just have to spell it the long way:
var x:Int {
get throws { ... }
}
The only other reasonable alternative IMO is:
var x:Int throws
Somehow I missed this thread when it originally happened.
One piece of concrete feedback:
I’m not sure what you’re imagining by “optimizations” here, but it’s not actually reasonable to try to prevent errors from being thrown between the start and end of an access. For example, the function taking the inout
parameter might itself just be a throwing function. This creates a semantic problem if we’re required to call the setter because we might end up with multiple errors in flight at once.
I think the best solution is to revise the semantic model for all accesses to distinguish between aborting and completing an access, with the idea that aborting a mutation to storage defined with set
/ willSet
/ didSet
causes the accessor call to be skipped.
I like this as a shorthand for declaring both the getter and setter as throwing, as well as a means for declaring a shorthand read-only computed property as throwing.
Has there been any development regarding this topic?
How use of Optional type or Result is not sufficient enough ?
The same way as using Optional
or Result
everywhere instead of throws
is not sufficient enough: The former loses the information about the reason why the desired outcome wasn't achieved and the latter is A: cumbersome to deal with and B: in case of a settable property allows one to set the value to an error, which is nonsensical in cases where the value itself is never meant to contain an error.
The code at the caller site would still had to handle failure , either it throwing or optional result. I doubt that desired behaviour can be transparent.
I think we still have a semantic problem of throwing setter used in tandem with throwing mutator (it even lurks into Modify Accessors).
I don’t see how you find this:
func doSomethingDangerous() throws {
try callThrowingMethod()
}
Behaviorally different from this:
func doSomethingDangerous() throws {
try accessThrowingProperty
}
Do you mean the defer
pitfall? In my opinion, this is a general problem with coroutines that has less to do with property accessors and more to do with the way coroutines are (or, more precisely, aren’t) supposed to interact with errors. If we had throwing modify
accessor, its apparent throws
-ness would simply no longer be dependent upon the yield
ed block, no? Or, maybe I misunderstood you. Could you, please, elaborate?
Even with get-set
var foo: Foo {
get { ... }
set throws { ... }
}
try foo.throwSomething()
as is, even if throwSomething
throws, the set
is invoked.
If set
also throws, we're now left with 2 distinct exceptions, which is quite a problem.
We may say, if throwSomething
throws, then set
is not called, but that is vastly different from other accessors (especially with stored variable). We may add new DoubleException: Error
but it gets clunky really easily.
There's also a question if set
should be called in the first place, but to say no would be source breaking. So one should weigh that option rather carefully.
There’s still more work to do :-) I think the two remaining things to do is restore LValueAccessKind and support _read/_modify.
I might miss something from the modify thread but why is it a problem?
Assume we had typed throws and both set
and the method would throw different error types such as A
and B
. Then either we will get a variadic generic Unit
type which itself conforms to Error
when all its generic type parameters also conform to Error
, or we fallback to the plain Error
instead if there is no Unit
yet.
do {
try foo.mutateAndThrow()
} catch is Unit<A, B> {
...
}
do {
try foo.mutateAndThrow()
} catch is Error {
switch error {
case let e as A:
...
case let e as B:
...
default:
...
}
}
With the mentioned features we can let the compiler infer the error type automatically and box it if necessary. For now it should be fine to fallback to Error
no?
Wait, why would it be source breaking when the behavior is only present in throwing setters, which are purely additive?
Given a non-throwing settable property, what happens if a mutating method throws? Is the setter called? If so, what is the newValue
equal to?
Nothing is wrong really. Though I'd feel weird if the Unit
is introduced solely for this usage.
And if we're to change the set
semantic we might as well do that. If I interpret @John_McCall's post here correctly, that may not be off the table. Though after re-reading it, I could be totally off the mark there .
I mean that we can either introduce set throws
with different semantic and be inconsistent with set
, or retroactively change set
semantic which is source breaking (or do something else).
The setter is called with the value being whatever it is right before the throw happens.
Same, therefore I hope if we eventually get that type (not clear yet), it will solve more problems than this one.
Now I see where the dual-error problem comes from. The compounding error problem exists outside this use case, though. I've found myself repeatedly hitting this design problem:
init(from decoder: Decoder) throws {
let container = decoder.singleValueContainer()
if let firstTry = try? container.decode(First.self) {
// ...
} else if let secondTry = try? container.decode(Second.self) {
// ...
} else {
// What exactly do I throw here?
}
}
On one hand, I would want to throw an aggregate error that contains the errors from each attempt, so that diagnosing the issue becomes easier. On the other hand, I'd be exposing a whole lot of implementation details by doing that and my gut tells me to define my own Error
type and throw that.
Regardless of what I'd choose, the key reason why this situation is not a fundamental problem is that I do indeed have a choice and the compiler forces me to choose.
What if the compiler would also force me to choose in this exact manner in case I declared a throwing setter? The first thing that comes to mind is promoting the newValue
to a Result
if the setter throws
:
struct A {
enum Error: Swift.Error {
case somethingWentWrong
}
var name: String
mutating func setNameAndThrow(to name: String) throws {
self.name = name
throw Error.somethingWentWrong
}
}
struct B {
var a: A {
get { A(name: "John Appleseed") }
set throws {
// `newValue` has the type `Result<A, Error>`
switch newValue {
case .success(let a):
// Do the actual setting or throw something custom
case .failure(let error):
// Throw the error directly or otherwise make the same choice as above.
}
}
}
var b: B()
try b.a.setNameAndThrow(to: "Bob Dylan")
Meanwhile, in the parallel universe where function parameters can also be marked as throwing, the newValue
is a throwing parameter instead of a Result
.