diff --git a/examples/check-expression/check_expression.bal b/examples/check-expression/check_expression.bal index 702677244d..6d68881fa4 100644 --- a/examples/check-expression/check_expression.bal +++ b/examples/check-expression/check_expression.bal @@ -1,18 +1,24 @@ import ballerina/io; -// Convert `bytes` to a `string` value and then to an `int` value. function intFromBytes(byte[] bytes) returns int|error { + string|error res = string:fromBytes(bytes); + // Explicitly check if the result is an error and + // immediately return if so. + if res is error { + return res; + } + return int:fromString(res); +} - // Use `check` with an expression that may return `error`. - // If `string:fromBytes(bytes)` returns an `error` value, `check` - // makes the function return the `error` value here. - // If not, the returned `string` value is used as the value of the `str` variable. +// Same as `intFromBytes` but with `check` instead of explicitly checking for error and returning. +function intFromBytesWithCheck(byte[] bytes) returns int|error { string str = check string:fromBytes(bytes); - return int:fromString(str); } public function main() { - int|error res = intFromBytes([104, 101, 108, 108, 111]); - io:println(res); + int|error res1 = intFromBytesWithCheck([104, 101, 108, 108, 111]); + io:println(res1); + int|error res2 = intFromBytes([104, 101, 108, 108, 111]); + io:println(res2); } diff --git a/examples/check-expression/check_expression.md b/examples/check-expression/check_expression.md index 0e5ff11344..df47a34c14 100644 --- a/examples/check-expression/check_expression.md +++ b/examples/check-expression/check_expression.md @@ -1,9 +1,9 @@ # Check expression -`check E` is used with an expression `E` that might result in an `error` value. If `E` results in an `error` value , then, `check` makes the function return that `error` value immediately. - -The type of `check E` does not include `error`. The control flow remains explicit. +If an expression can evaluate to an error value, you can use the `check` expression to indicate that you want the execution of the current block to terminate with that error as the result. This results in the error value being returned from the current function/worker, unless the `check` expression is used in a failure-handling statement (e.g., statement with on fail, retry statement). ::: code check_expression.bal ::: -::: out check_expression.out ::: \ No newline at end of file +::: out check_expression.out ::: + ++ [`check` semantics](https://ballerina.io/learn/concurrency/#check-semantics) diff --git a/examples/check-expression/check_expression.out b/examples/check-expression/check_expression.out index aed09b016e..b763450360 100644 --- a/examples/check-expression/check_expression.out +++ b/examples/check-expression/check_expression.out @@ -1,2 +1,3 @@ $ bal run check_expression.bal error("{ballerina/lang.int}NumberParsingError",message="'string' value 'hello' cannot be converted to 'int'") +error("{ballerina/lang.int}NumberParsingError",message="'string' value 'hello' cannot be converted to 'int'") diff --git a/examples/error-reporting/error_reporting.bal b/examples/error-reporting/error_reporting.bal index 7b0b31ec52..e85a2ff841 100644 --- a/examples/error-reporting/error_reporting.bal +++ b/examples/error-reporting/error_reporting.bal @@ -1,31 +1,57 @@ import ballerina/io; -// Parses a `string` value to convert to an `int` value. This function may return error values. -// The return type is a union with `error`. -function parse(string s) returns int|error { +type Person record { + string name; + int age; +}; - int n = 0; - int[] cps = s.toCodePointInts(); - foreach int cp in cps { - int p = cp - 0x30; - if p < 0 || p > 9 { - // If `p` is not a digit construct, it returns an `error` value with `not a digit` as the error message. - return error("not a digit"); +function validatePeople(Person[] people) returns error? { + if people.length() == 0 { + // Create an error value specifying only the error message. + return error("Expected a non-empty array"); + } + foreach Person p in people { + io:println("Validating ", p.name); + error? err = validatePerson(p); + if err is error { + // Create a new error value with the validation error as the cause + // and the `Person` value for which validation failed in the detail mapping. + return error("Validation failed for a person", err, person = p); } - n = n * 10 + p; } - return n; } -public function main() { - // An `int` value is returned when the argument is a `string` value, which can be parsed as an integer. - int|error x = parse("123"); +function validatePerson(Person person) returns error? { + int age = person.age; + if age < 0 { + // If validation fails for age, create a new error value specifying + // an error message and the age value for which validation failed. + return error("Age cannot be negative", age = age); + } +} - io:println(x); +public function main() { + Person[] people = [ + {name: "Alice", age: 25}, + {name: "Bob", age: -1}, + {name: "Charlie", age: 30} + ]; + error? err = validatePeople(people); + if err is error { + printError(err); + } +} - // An `error` value is returned when the argument is a `string` value, which has a character that is not a digit. - int|error y = parse("1h"); +// Helper function to print internals of error value. +function printError(error err) { + io:println("Message: ", err.message()); + io:println("Detail: ", err.detail()); + io:println("Stack trace: ", err.stackTrace()); - io:println(y); + error? cause = err.cause(); + if cause is error { + io:println("Cause:"); + printError(cause); + } } diff --git a/examples/error-reporting/error_reporting.md b/examples/error-reporting/error_reporting.md index 2edbcb6b8a..8a4faeb247 100644 --- a/examples/error-reporting/error_reporting.md +++ b/examples/error-reporting/error_reporting.md @@ -1,10 +1,20 @@ # Errors -Ballerina does not have exceptions. Errors are reported by functions returning `error` values. -`error` is its own basic type. The return type of a function that may return an `error` value will be a union with `error`. +Ballerina does not have exceptions. Instead functions report invalid states by returning error values. Each error value has, +1. Message, a human-readable `string` value describing the error. +2. Cause, which is an `error` value if this error was caused due to another error, which needs to be propagated, otherwise nil. +3. Detail, a mapping value consisting of additional information about the error. +4. Stack trace, a snapshot of the state of the execution stack when the error value was created. -An `error` value includes a `string` message. An `error` value includes the stack trace from the point at which the error is constructed (i.e., `error(msg)` is called). Error values are immutable. +Error values are immutable. + +You can create a new error value using an error constructor. As the first argument to the error constructor, it expects the message string. As the second argument, you can optionally pass in an `error?` value for cause. Subsequent named arguments, if specified, will be used to create the detail mapping. The stack trace is provided by the runtime. ::: code error_reporting.bal ::: -::: out error_reporting.out ::: \ No newline at end of file +::: out error_reporting.out ::: + +## Related links +- [Error subtyping](https://ballerina.io/learn/by-example/error-subtyping/) +- [Error cause](https://ballerina.io/learn/by-example/error-cause/) +- [Error detail](https://ballerina.io/learn/by-example/error-detail/) diff --git a/examples/error-reporting/error_reporting.out b/examples/error-reporting/error_reporting.out index 690a047e44..609403ab5e 100644 --- a/examples/error-reporting/error_reporting.out +++ b/examples/error-reporting/error_reporting.out @@ -1,3 +1,10 @@ $ bal run error_reporting.bal -123 -error("not a digit") +Validating Alice +Validating Bob +Message: Validation failed for a person +Detail: {"person":{"name":"Bob","age":-1}} +Stack trace: [callableName: validatePeople fileName: error_reporting.bal lineNumber: 20,callableName: main fileName: error_reporting.bal lineNumber: 40] +Cause: +Message: Age cannot be negative +Detail: {"age":-1} +Stack trace: [callableName: validatePerson fileName: error_reporting.bal lineNumber: 30,callableName: validatePeople fileName: error_reporting.bal lineNumber: 16,callableName: main fileName: error_reporting.bal lineNumber: 40] diff --git a/examples/error-subtyping/error_subtyping.bal b/examples/error-subtyping/error_subtyping.bal index e11053e0dc..5cfb3623b2 100644 --- a/examples/error-subtyping/error_subtyping.bal +++ b/examples/error-subtyping/error_subtyping.bal @@ -1,21 +1,53 @@ import ballerina/io; -// `distinct` creates a new subtype. -type XErr distinct error; -type YErr distinct error; +type InvalidIntDetail record {| + int value; +|}; -type Err XErr|YErr; +// Subtype of `InvalidIntDetail`. +type InvalidI32Detail record {| + int:Signed32 value; +|}; -// The name of the distinct type can be used with the `error` constructor to create an error value -// of that type. `err` holds an `error` value of type `XErr`. -Err err = error XErr("Whoops!"); +// Error with the `InvalidIntDetail` type as the detail type. +type InvalidIntError error; -function desc(Err err) returns string { - // The `is` operator can be used to distinguish distinct subtypes. - return err is XErr ? "X" : "Y"; +// `error` with `InvalidI32Detail` as the detail type. Thus it is a subtype of `InvalidIntError`. +type InvalidI32Error error; -} +// Distinct error with the `InvalidIntDetail` type as the detail type and a unique type ID. +// Therefore, this is a proper subtype of `InvalidIntError`, but doesn't have a subtype relationship +// with `AnotherDistinctIntError` because they have different type IDs. +type DistinctIntError distinct error; + +// Another `error` with `InvalidIntDetail` as the detail type and different type ID to `DistinctIntError` +// This is also a proper subtype of `InvalidIntError`, but doesn't have a subtype relationship with `DistinctIntError` +type AnotherDistinctIntError distinct error; public function main() { - io:println(desc(err)); + InvalidI32Error e1 = createInvalidI32Error(5); + io:println(e1 is InvalidIntError); + + InvalidIntError e2 = createInvalidIntError(5); + io:println(e2 is DistinctIntError); + + DistinctIntError e3 = createDistinctInvalidIntError(5); + // This is true because `InvalidInt` is not a distinct type, thus it ignores the type id of `e3`. + io:println(e3 is InvalidIntError); + + // This is false because `DistinctIntError` and `AnotherDistinctIntError` have different type ids. + io:println(e3 is AnotherDistinctIntError); +} + +// Helper functions to create errors. +function createInvalidIntError(int value) returns InvalidIntError { + return error("Invalid int", value = value); +} + +function createDistinctInvalidIntError(int value) returns DistinctIntError { + return error("Invalid int", value = value); +} + +function createInvalidI32Error(int:Signed32 value) returns InvalidI32Error { + return error("Invalid i32", value = value); } diff --git a/examples/error-subtyping/error_subtyping.md b/examples/error-subtyping/error_subtyping.md index f2d69e7966..6cd53ee3a6 100644 --- a/examples/error-subtyping/error_subtyping.md +++ b/examples/error-subtyping/error_subtyping.md @@ -1,9 +1,16 @@ # Error subtyping -`distinct` creates a new subtype and can be used to define subtypes of `error`. The name of the distinct `error` type can be used with the error constructor to create an `error` value of that type. +If we want to identify if a given `error` type (say `ESub`) is a subtype of another error type (say `ESuper`), first we need to check if `ESuper` is a distinct error type. If it is not then `ESub` is a subtype if and only if the detail type of `ESub` is a subtype of the detail type of `ESuper`. -Works like a nominal type. The `is` operator can be used to distinguish distinct subtypes. Each occurrence of `distinct` has a unique identifier that is used to tag instances of the type. +If more explicit control over error type relations is desired you can use `distinct` error types. Each declaration of a distinct error type has a unique type ID. If `ESuper` is a distinct error type there is the additional requirement that type ID of `ESub` must belong to the type ID set of `ESuper` for it to be a subtype. + +Note that you can create subtypes of distinct error types by intersecting them with other error types. + +```ballerina ::: code error_subtyping.bal ::: -::: out error_subtyping.out ::: \ No newline at end of file +::: out error_subtyping.out ::: + +## Related links +- [Type intersection for error types](https://ballerina.io/learn/by-example/error-type-intersection/) diff --git a/examples/error-subtyping/error_subtyping.out b/examples/error-subtyping/error_subtyping.out index d0ed13cfaa..7f8675bb19 100644 --- a/examples/error-subtyping/error_subtyping.out +++ b/examples/error-subtyping/error_subtyping.out @@ -1,2 +1,5 @@ $ bal run error_subtyping.bal -X +true +false +true +false diff --git a/examples/error-type-intersection/error_type_intersection.bal b/examples/error-type-intersection/error_type_intersection.bal index cd904bcb04..7049dcc62f 100644 --- a/examples/error-type-intersection/error_type_intersection.bal +++ b/examples/error-type-intersection/error_type_intersection.bal @@ -1,33 +1,47 @@ import ballerina/io; -type IOError distinct error; +type InputErrorDetail record {| + int|string value; +|}; -type FileErrorDetail record { - string filename; -}; +type NumericErrorDetail record {| + int|float value; +|}; -// The `FileIOError` type is defined as an intersection type using the `&` notation. -// It is the intersection of two error types: `IOError` and `error`. -// An error value belongs to this type if and only if it belongs to both `IOError` -// and `error`. -type FileIOError IOError & error; +type InputError error; + +type NumericError error; + +type DistinctInputError distinct error; + +type DistinctNumericError distinct error; + +// `NumericInputError` has detail type, `record {| int value |}`. +type NumericInputError InputError & NumericError; + +// `DistinctNumericInputError` has type ids of both `DistinctInputError` and `DistinctNumericError`. +type DistinctNumericInputError DistinctInputError & DistinctNumericError; + +function createNumericInputError(int value) returns NumericInputError { + return error("Numeric input error", value = value); +} + +function createDistinctNumericInputError(int value) returns DistinctNumericInputError { + return error("Distinct numeric input error", value = value); +} public function main() { - // In order to create an error value that belongs to `FileIOError`, the `filename` - // detail field must be provided. - FileIOError fileIOError = error("file not found", filename = "test.txt"); - - // `fileIOError` belongs to both `IOError` and `error`. - io:println(fileIOError is IOError); - io:println(fileIOError is error); - - // An `IOError` value will not belong to `FileIOError` if it doesn't belong to - // `error`. - IOError ioError = error("invalid input"); - io:println(ioError is FileIOError); - - // Similarly, an error value belonging to `error` will not belong - // to `FileIOError` if it doesn't belong to `IOError`. - error fileError = error("cannot remove file", filename = "test.txt"); - io:println(fileError is FileIOError); + NumericInputError e1 = createNumericInputError(5); + // `e1` belong to `InputError` since its detail type is a subtype of `InputErrorDetail`. + io:println(e1 is InputError); + + // `e1` doesn't belong to `DistinctInputError` since it doesn't have the type id of `DistinctInputError`. + io:println(e1 is DistinctInputError); + + DistinctNumericInputError e2 = createDistinctNumericInputError(5); + // `e2` belong to `InputError` since it's detail type is a subtype of `InputErrorDetail`. + io:println(e2 is InputError); + + // `e2` belong to `DistinctInputError` since it's type id set include the type id of `DistinctInputError`. + io:println(e2 is DistinctInputError); } diff --git a/examples/error-type-intersection/error_type_intersection.md b/examples/error-type-intersection/error_type_intersection.md index 2b65eb1bb1..24b8631453 100644 --- a/examples/error-type-intersection/error_type_intersection.md +++ b/examples/error-type-intersection/error_type_intersection.md @@ -1,7 +1,9 @@ # Type intersection for error types -You can define an error type that is both a subtype of a `distinct` error type and has additional constraints on the detail fields using intersection types. +If you intersect two `error` types, the resulting type's detail type is the intersection of the detail types of both types. Furthermore, if any of the types being intersected is a distinct type, then the resultant type's type ID set includes all the type IDs of that type. Thus it is a subtype of both types and this is how you create subtypes of `distinct` error types. ::: code error_type_intersection.bal ::: ::: out error_type_intersection.out ::: + ++ [Error subtyping](https://ballerina.io/learn/by-example/error-subtyping/) diff --git a/examples/error-type-intersection/error_type_intersection.out b/examples/error-type-intersection/error_type_intersection.out index e315509605..20c659f272 100644 --- a/examples/error-type-intersection/error_type_intersection.out +++ b/examples/error-type-intersection/error_type_intersection.out @@ -1,5 +1,5 @@ $ bal run error_type_intersection.bal true -true -false false +true +true diff --git a/examples/index.json b/examples/index.json index f37003bbf3..d0e4ceb86b 100644 --- a/examples/index.json +++ b/examples/index.json @@ -1376,6 +1376,13 @@ "verifyOutput": true, "isLearnByExample": true }, + { + "name": "Trap expression", + "url": "trap-expression", + "verifyBuild": true, + "verifyOutput": true, + "isLearnByExample": true + }, { "name": "Type intersection for error types", "url": "error-type-intersection", diff --git a/examples/panics/panics.md b/examples/panics/panics.md index 932fb94a60..4dcee684b1 100644 --- a/examples/panics/panics.md +++ b/examples/panics/panics.md @@ -1,9 +1,7 @@ # Panics -Ballerina distinguishes normal errors from abnormal errors. Normal errors are handled by returning error values. Abnormal errors are handled using the panic statement. Abnormal errors should typically result in immediate program termination. - -E.g., A programming bug or out of memory. A panic has an associated error value. +Ballerina distinguishes normal errors from abnormal errors. Normal errors are handled by returning error values. This signals to the caller that they must handle the error. In contrast, abnormal errors such as division by 0 and out-of-memory are typically unrecoverable errors and we need to terminate the execution of the program. This is achieved by a panic statement with an expression that results in an error value such as the error constructor. At runtime, evaluating a panic statement first creates the error value by evaluating the expression and then starts stack unwinding, unless it encounters a `trap` expression. ::: code panics.bal ::: -::: out panics.out ::: \ No newline at end of file +::: out panics.out ::: diff --git a/examples/trap-expression/trap_expression.bal b/examples/trap-expression/trap_expression.bal new file mode 100644 index 0000000000..eb0297d7a4 --- /dev/null +++ b/examples/trap-expression/trap_expression.bal @@ -0,0 +1,27 @@ +import ballerina/io; + +function hereBeDragons() returns int { + // Trigger a panic deep within the call stack. + alwaysPanic(); +} + +// Return type `never` indicate that this function will never return normally. I.e., it will always panic. +function alwaysPanic() returns never { + panic error("deep down in the code"); +} + +public function main() { + // Division by 0 triggers a panic. + int|error result = trap 1 / 0; + if result is error { + io:println("Error: ", result); + } + // Calling the `hereBeDragons` function triggers a panic deep within the call stack, which is trapped here. + int|error result2 = trap hereBeDragons(); + if result2 is error { + io:println("Error: ", result2); + } + // This will trigger a panic which is not trapped. Thus it will terminate the program. + int result3 = hereBeDragons(); + io:println("Result: ", result3); +} diff --git a/examples/trap-expression/trap_expression.md b/examples/trap-expression/trap_expression.md new file mode 100644 index 0000000000..7699b557b6 --- /dev/null +++ b/examples/trap-expression/trap_expression.md @@ -0,0 +1,9 @@ +# Trap expression + +If you have an expression such as a function call that can potentially trigger a panic you can use a `trap` expression to prevent further unwinding of the stack. Then if the evaluation of the expression triggers a panic you will get the `error` value associated with the panic. Otherwise, you will get the result of the expression. + +::: code trap_expression.bal ::: + +::: out trap_expression.out ::: + ++ [Panics](https://ballerina.io/learn/by-example/panics/) diff --git a/examples/trap-expression/trap_expression.metatags b/examples/trap-expression/trap_expression.metatags new file mode 100644 index 0000000000..da44b3deb8 --- /dev/null +++ b/examples/trap-expression/trap_expression.metatags @@ -0,0 +1,2 @@ +description: This BBE demonstrates how the trap expression is used in Ballerina to handle panics +keywords: ballerina, ballerina by example, bbe, error, panic, trap diff --git a/examples/trap-expression/trap_expression.out b/examples/trap-expression/trap_expression.out new file mode 100644 index 0000000000..84e1a561ae --- /dev/null +++ b/examples/trap-expression/trap_expression.out @@ -0,0 +1,7 @@ +$ bal run trap_expression.bal +Error: error("{ballerina}DivisionByZero",message=" / by zero") +Error: error("deep down in the code") +error: deep down in the code + at trap_expression:alwaysPanic(trap_expression.bal:10) + trap_expression:hereBeDragons(trap_expression.bal:5) + trap_expression:main(trap_expression.bal:25)