Checked Exceptions Lite Using Nullable Reference Pattern #2052
Replies: 24 comments
-
I’d also propose that this should probably be called something other than checked exceptions, given the baggage associated with that term. Maybe “checkable exceptions” to go with “nullable reference types”? |
Beta Was this translation helpful? Give feedback.
-
Sounds like something that can be accomplished with an analyzer. |
Beta Was this translation helpful? Give feedback.
-
You could technically say the same thing about nullable reference types. In a lot of ways, they are just a special case of this, preventing an uncaught NullReferenceException. |
Beta Was this translation helpful? Give feedback.
-
@cjbush |
Beta Was this translation helpful? Give feedback.
-
Take a look at Ander Heijlsbergs thoughts on checked exceptions: |
Beta Was this translation helpful? Give feedback.
-
Also worth reading are Joe Duffy's thoughts on checked exceptions and error models in general. http://joeduffyblog.com/2016/02/07/the-error-model/ To be honest, I don't think we as programmers have any good system for handling errors at all, although of all the mainstream languages I think Rust has the best system. |
Beta Was this translation helpful? Give feedback.
-
@jnm2 No, obviously the specific syntax enhancements nullable reference types provide can't be accomplished with an analyzer, but the concept of preventing unhandled nulls does fall into the domain of what analyzers are there for. @YairHalberstadt I've read both in the past. That's why this proposal approaches them from a different perspective, where the specific exceptions that a method throws aren't baked into the API the way they are in Java. And because this would be an opt-in feature and all you get is a compiler warning (instead of compiler errors like in Java), you shouldn't have the same scalability and versioning issues. |
Beta Was this translation helpful? Give feedback.
-
Yes, you could. Although the flow analysis within methods would make it quite involved, and without additional syntax you'd be required to use attributes which would make it a lot more verbose and limited only to where you can apply attributes, which would be a lot more limiting. And ultimately the team decided that the problem was important enough to address directly in the language, although I kind of wish they at least made enforcement through analyzers which would make the rules more flexible. Your proposal doesn't involve special syntax or flow analysis. You mark the method with an attribute, and simple analyzers can already track whether or not method invocations occur within a try block and can correlate that with a given catch block. In the end the best case scenario, given a near immediate champion and somewhat quick process through language design meetings, you're probably talking a good year or more before a feature like this might be implemented in the language proper. An analyzer could probably be whipped up in a matter of days. And that's likely where a language proposal would need to start in order to demonstrate viability anyway. |
Beta Was this translation helpful? Give feedback.
-
I think the chief point here is Anders one. If you don't have anything useful to say on an issue, don't say anything. This proposal doesnt feel like it really solves the issue sufficiently. As such, I think best to leave it as an analyser, rather than committing to a language change at this point. |
Beta Was this translation helpful? Give feedback.
-
Maybe I'm missing something from your proposal, but how is this different than Java's checked exceptions? The only difference I see is that it's a warning instead of an error. (Also add me to the list of people who think this should just be an analyzer.) |
Beta Was this translation helpful? Give feedback.
-
And the language team treats introducing new warnings no different from introducing new errors due to the Simply put, any proposal to add new warnings on code that is valid today is always gonna be a non-starter. |
Beta Was this translation helpful? Give feedback.
-
@Joe4evr That's why I proposed it should be an opt-in feature, the way nullable reference types are. Which is also something you wouldn't be able to get with an analyzer, as far as I can see. @PathogenDavid The other difference is that the exceptions that are thrown by the method aren't part of the method's signature, and so changing the exceptions that a method throws doesn't introduce a breaking API change the way it does with checked exceptions in Java. |
Beta Was this translation helpful? Give feedback.
-
Being able to opt-in is, if anything, a defining feature of analyzers. I'll further state that I don't think that it's really possible to bring checked exceptions to C# in any form. The problem is that .NET has no concept of "unchecked" exceptions which shouldn't require being declared. There's no real way for the compiler to selectively enforce the concept of adhering to checked exceptions when pretty much every method can potentially throw some flavor of exception. The C# compiler doesn't know a |
Beta Was this translation helpful? Give feedback.
-
The language design folks have made it very clear on numerous occasions that they're not going to fork the language into a myriad of dialects. The only reason that this is happening at all for nullable reference types is that the benefit of eliminating causes of |
Beta Was this translation helpful? Give feedback.
-
Hi @cjbush , I've just wanted to create my own proposal which is very similar to yours ;) ... then I have found your proposal and decided to write here void MySafeFunction() throws {
// All is good, we explicitly say that method can failed
// and none of them are being handled properly or you should propagate exception explicitly up to next callee
File.WriteAllText("C:\helloworld.txt", "hello world");
}
void MyUnsafeFunction() {
// Generates an error that this call can throw IOException, UnauthorizedAccessException, etc
// and none of them are being handled properly or you should propagate exception explicitly up to next callee
File.WriteAllText("C:\helloworld.txt", "hello world");
}
void MyFunctionThatHandlesExceptions(){
try {
// No errors, because all exceptions that can be thrown here are caught
File.WriteAllText("C:\helloworld.txt", "hello world");
} catch (Exception ex) {
// do stuff
}
}
// Propagates all the exceptions that us and our callees throw up to our caller.
// This will converted to:
// [Throws]
// void MyFunctionThatPassesTheBuck()
// or even compiler could emit annotations for all throwing exceptions
// [Throws(ThisWillExplodeException)]
// void MyFunctionThatPassesTheBuck()
void MyFunctionThatPassesTheBuck() throws {
// No warnings generated because we're explicitly stating that we're passing any exceptions up
// to our caller
File.WriteAllText("C:\helloworld.txt", "hello world");
throw new ThisWillExplodeException("You knew this would happen, so why did you call me?");
}
// Generate warnings, because we are using old style method signature if we use some special flag
// and no warnings if flag is not specified
void MyFunctionThatsATickingTimeBomb() => throw new ThisWillExplodeException(); With such changes we have as backward compatibility as well as propageting throws keyword everywhere if method can throw something ;) |
Beta Was this translation helpful? Give feedback.
-
@HaloFour Compiler is the main code analyzer !! ;) |
Beta Was this translation helpful? Give feedback.
-
@theunrepentantgeek I do not think that it would be dialect anytime ... It is three step adaptation and I do not think that community will argue ;) |
Beta Was this translation helpful? Give feedback.
-
It's also the most expensive to change, by several orders of magnitude. The compiler needs to go through proper specification and prioritization and any chance isn't likely to happen for over a year, at best. You or anyone else can develop and ship an analyzer right now. Something that scans custom attributes and produces diagnostics is the bread and butter of analyzers and even if the team was interested in such tooling it would likely be developed as an analyzer and not part of the compiler proper. Java-style checked exceptions will never come to C# because they are just downright awful. Even most JVM languages flat out ignore them.
NRTs are a compiler dialect. It doesn't matter that the option might go away sometime in the future. The team has been very upfront about this, and they've been very vocal on the subject that such dialects are only to be introduced in cases that demonstrate exceptional value. |
Beta Was this translation helpful? Give feedback.
-
@HaloFour Java exceptions is not scaled, I agree ...
@HaloFour I can benefit from it only in case when all developer will use it, because it will be based on annotations ... Okay, I need to think on it a little bit ... |
Beta Was this translation helpful? Give feedback.
-
No. In many cases it's not that a method declares specific exceptions that are a problem in Java, it's the fact that a method can throw anything at all. Or that other methods can't. Particularly lambdas. If a method accepts a lambda and that method cannot throw then you're still forced to handle the exception within the lambda, which you're often completely incapable of doing. It forces you to handle the exception despite often not being able to do anything useful.
I vehemently disagree. So do a lot of other people, including the original architect of C#. There's a reason .NET did not copy that aspect of Java, despite copying so many others. Even 20 years ago checked exceptions was known to be terrible. That same reason is why Clojure, Scala and Kotlin completely ignore checked exceptions in the JVM. |
Beta Was this translation helpful? Give feedback.
-
I'm not really in favour of explicitly stating any What I am in favour of, is knowing all the possible exceptions a method can throw. So this might be a long shot, but would it be possible to store exceptions that a method or property can throw in its metadata by the compiler? Currently, we can specify which exceptions a method can throw by using the xml-doc If a method would expose what exceptions it can throw, there is no need to recursively look them up since all methods you'd use would have that information readily available. Only a recursive search would be needed in a single assembly. Since the exception metadata would be implicit data rather than explicit data, I don't think it would break any contract. The biggest downside I see is a if assembly A references B which references C and you want to update C. You'd have to also recompile B for A to see the correct exceptions. It would not be possible to swap the assembly C since B would have the wrong metadata of C embedded in it. I don't know if this is a requirement of C# or not that metadata should always be consistent. |
Beta Was this translation helpful? Give feedback.
-
The downside is much closer than your transitive assembly example. Consider this code:
It's impossible to statically analyse the method Is it going to throw Essentially this is exactly the same situation as the problem with versioning that prompted Anders and his team to decide that checked exceptions were not a good solution, way back in 1999.
I don't think this can be reliably determined except at runtime. |
Beta Was this translation helpful? Give feedback.
-
Or could it be any of the various exceptions that can happen to be thrown by the runtime? This is important because .NET doesn't differentiate between "checked" and "unchecked" exceptions like other runtimes do. |
Beta Was this translation helpful? Give feedback.
-
Haha, I knew it couldn't be that simple, otherwise I would imagine it would have already been implemented.
Hmm, I see know that you are correct. Exceptions can never be part of a contract as soon as you introduce any dependency, be it an interface or class. A mock implementation for example for the If I now return to the original question, I don't think there would be an issue to implement that behaviour when the exceptions are known. The problem, as @theunrepentantgeek mentions, is actually determining which exceptions that can be thrown. Even if you disregard interfaces it would still generate a lot of false positives and would leave a lot open. var array = new string[] { "Hello", "World" };
var world = array[1];
// should this warn about not catching IndexOutOfRangeException?
// we know it can't be thrown, but the compiler cannot
var iList = array as IList<string>;
iList.Add("!"); //throws NotSupportedException
// should it know about the exception of the specific Array implementation?
// if so, should it scan all assemblies for any implementation? Seems a bit excessive and impossible for a plugin architecture
// should it warn about NullReferenceException as iList could be null? |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
While checked exceptions have always been considered a bad idea, with good reason, I think that a lot of the issues arise not so much with the concept itself, but with Java's implementation of checked exceptions. What I'm proposing would leverage functionality very similar to the way the compiler handles nullable reference types, in that the developers have the option to have the compiler warn them when a callee throws an exception that they're not handling. For example:
I think that something along these lines would give developers much finer grained control over their error handling, especially when dealing with third party or poorly documented libraries. The issue that comes up often is that you have no idea what your callee can possibly throw, and end up having to just do a general catch-all at much lower levels that is usually deemed appropriate. And since this could potentially be an opt-in feature the way nullable reference types are, I don't think it would break anybody's existing workflow.
Thanks and keep up the great work!
Beta Was this translation helpful? Give feedback.
All reactions