Currently when using NSException.raise from Swift the API returns Void while the runtime behaviour of this API in Swift is to crash (i.e. Never returns). Is this a bug?
Every ObjC method can be swizzled, there could be situations where raise does not crash the App at runtime.
Every ObjC method can be swizzled
True, but I think this is more of a case of the method far predating Swift, and indeed the __noreturn__ attribute in C-based languages, and no one has gone back to add it. Notably, this distinction is much more important in Swift than C-based languages.
However, there’s a further wrinkle here. In Objective-C, messaging nil is a no-op, so calling -raise on an NSException * that happens to be nil will return.
Given that, I don’t think it’s as simple as adding __noreturn__ to the Objective-C method. But I guess you could refine it for Swift and mark the Swift version as returning Never, because in Swift you can only call the method on a non-optional exception.
soumyamahunt, I think it’d be reasonable for you to file a bug requesting an improvement here. I’m not sure what’ll happen, but it doesn’t hurt to ask.
Note that this is an Apple bug, not a Swift bug, because NSException is very much an Apple thing.
Please post your bug number, just for the record.
Share and Enjoy
Quinn “The Eskimo!” @ DTS @ Apple
Since it is illegal for an exception to pass through a Swift frame, I would expect the solution to be to mark -raise as unavailable in Swift.
Or at least deprecated without replacement, yeah. At any rate, that's a question for the Foundation code owners.
Hmmmm, that depends on your definition of “illegal”. It’s illegal for it to pass through a Swift frame and then be caught. It’s fine for it to pass through a Swift frame and then terminate the process. And I can think of at least a few good reasons why Swift code might want to do that.
Agreed.
Share and Enjoy
Quinn “The Eskimo!” @ DTS @ Apple
I'll bite. There is no valid way to call a function whose only effect is to trigger undefined behaviour. Because the effect of throwing an exception through a Swift stack frame is undefined, it is possible that e.g. a developer-supplied uncaught exception handler, or even a catch block higher up the stack, won't be called. The right way to terminate a process abnormally in Swift is to call fatalError() or a related function.
Could a wrapper be provided for crashing with a NSException like the following
/// Unconditionally prints a diagnostic for a ``NSException`` and stops execution.
///
/// - Note: If `nsexception.reason` is empty the underlying identifier will be extracted as the diagnostic
func fatalError(
_ nsexception: NSException,
file: StaticString = #file,
line: UInt = #line
) -> Never {
// `NSExceptionName` conforms to `RawRepresentable`
fatalError(
!(nsexception.reason?.isEmpty ?? true) ?
nsexception.reason! :
"NSException: \"\(nsexception.name.rawValue)\"",
file: file, line: line
)
}
Providing this wrapper like this to make it easier for developers to be more easily able to receive usable diagnostics when their code crashes without having to account for cases where there isn’t a indicated reason on their behalf
let demoException = NSException(
name: .genericException,
reason: "This is a demonstration Exception"
)
fatalError(demoException)
This seems like it should be an adequate NSException.raise() replacement, at least for situations where it should be treated as Never. Potentially NSException.raise() could move to NSException._raise() and point back but the old name produces the warning while the new name does not.
What would be the benefit of that over just calling fatalError()? You cannot meaningfully catch the exception in Swift, and code written in C++ or ObjC that catches it can call abort() or similar directly.
I'll bite. There is no valid way to call a function whose only effect is to trigger undefined behaviour. Because the effect of throwing an exception through a Swift stack frame is undefined
Yes, indeed. Also discussed here: