-
Notifications
You must be signed in to change notification settings - Fork 227
Description
Summary
This proposal suggests introducing explicit error handling primitives inspired by Zig and Swift. It aims to move Dart towards safer, checked exception handling without breaking existing unchecked exception flows, using the familiar try keyword as an expression.
1. Motivation
Current Dart exceptions are unchecked; a caller has no syntactic indicator that a function might throw. This leads to hidden runtime failures and "catch-all" debugging. This proposal adds visible fallibility to function signatures and call sites, making control flow explicit.
2. Proposed Syntax
Phase 1: The Fallible Marker (!)
We introduce a syntactic sugar ! to return types.
- Current:
Future<User> fetchUser()(Might throw, might return User). - Proposed:
Future<User>! fetchUser()(Explicitly returns User OR Error).
The ! acts as a compile-time flag. It forces the caller to acknowledge the potential failure.
Phase 2: Propagation (try Expression)
We overload the try keyword to act as a prefix operator when handling ! types.
Usage Rules:
- Propagation (Early Return): When used before a function call,
tryattempts to unwrap the value. If the function fails, it immediately returns the error to the caller (similar to Zig). - Visual Clarity: It distinguishes "dangerous" calls from standard calls at a glance.
3. Code Examples
A. Defining a Fallible Function
// The '!' indicates this function returns a String or throws an error
String! readFile(String path) {
if (!exists(path)) {
throw FileNotFoundException(path); // This becomes the error value
}
return "File Content";
}B. Propagation (The try Keyword)
In this scenario, we do not want to handle the error locally; we want to pass it up the stack automatically.
String! getConfig() {
// If readFile fails, the error is immediately returned from getConfig.
// Execution stops here. The 'try' keyword makes the exit point visible.
var content = try readFile("config.txt");
return parse(content);
}C. Handling Errors
To actually handle the error, we use the standard try/catch block, but the inner call remains explicit.
void main() {
try {
// We use 'try' here to acknowledge the fallible call.
// If it fails, it jumps to the catch block as usual.
var config = try getConfig();
print("Config loaded: $config");
} catch (e) {
// The compiler knows 'getConfig' throws, so this catch is strictly typed.
print("Failed to load config: $e");
}
}4. Backward Compatibility
To ensure non-breaking changes:
- Opt-in: Functions without
!remain "Unchecked" (standard Dart behavior). - Gradual Adoption: The analyzer can suggest converting
TypetoType!if it detects throwing behavior. - No Syntax Conflicts:
tryis currently only valid before a{block. Using it as a prefix to an expression (try func()) is syntactically unambiguous to the parser.