const std = @import( "std" ); fn print( comptime fmt :[]const u8 ) void { std.debug.print( fmt, .{} ); // _ = fmt; // XXX noisy confusion ; needs review // think the pattern is // \\ errdefer occurs when an error_enum or error_union is returned // \\ structs are not errors; // \\ returning a struct containing an error does not trigger errdefer } // caution, dead/unused functions are not compile warnings fn return_error() ErrorWhenSet { // XXX caution, beware errdefer { print( "errdefer occurs on return-error ( yes )\n" ); } return error.when; } fn return_error_union() ErrorWhenSet!void { // same as `!void` errdefer { print( "errdefer occurs on return-error-union ( yes )\n" ); } return return_error(); } // `errors` are instances of error-sets // `error-sets` are `enum(u16) { ... }` // `error-unions` are `union(enum) { :error-set, :Result }` // ** `error-unions` are the *resource* that must be handled ** fn try_error_union() !void { errdefer { print( "errdefer occurs on try-error-union ( yes )\n" ); } try return_error_union(); } // fn try_error() !void { // errdefer { print( "errdefer occurs on try-error ( yes )\n" ); } // try error.when; // syntax error, ha // } fn return_try_error_union() !void { errdefer { print( "errdefer occurs on return-try-error-union ( yes )\n" ); } return try return_error_union(); } fn return_it() !void { errdefer { print( "errdefer occurs on return ( yes )\n" ); } return return_error_union(); } fn break_it() !void { const ret = block: { errdefer { print( "errdefer occurs on break ( think so )\n" ); } break :block return_error_union(); }; _ = try ret; // return ret; } fn error_catch() !void { errdefer |e| { std.debug.print( "never {e}\n", .{ @errorName( e ) } ); } // _ = return_error(); // return_error() catch |e| { _ = e; }; return_error_union() catch {}; } const ErrorWhenSet = error { when }; fn return_error_literal() ErrorWhenSet { errdefer { print( "errdefer occurs on returning an error ( yes )\n" ); } return error.when; // see `return_error` above } const StructWithError = struct { e :ErrorWhenSet }; fn return_struct_with_error() StructWithError { errdefer { print( "errdefer does not occur on return-struct-containing-error ( no )\n" ); } print( "errdefer does not occur on return-struct-containing-error ASDF\n" ); return .{ .e = error.when }; } const StructWithErrorUnion = struct { e :ErrorWhenSet!void }; fn return_struct_with_error_union() StructWithErrorUnion { errdefer { print( "errdefer does not occur on return-struct-containing-error-union ( no )\n" ); } print( "errdefer does not occur on return-struct-containing-error-union ASDF\n" ); return .{ .e = return_error_union() }; } // .. can error_sets be passed as params ? fn is_success( v :ErrorWhenSet!void ) !void { try std.testing.expectEqual( void {}, v ); } fn is_failure( e :ErrorWhenSet!void ) !void { try std.testing.expectError( error.when, e ); } test "when errdefer" { try is_failure( return_error_union() ); try is_failure( try_error_union() ); // try is_failure( try_error() ); // bad syntax; `try error.x;` try is_failure( return_try_error_union() ); try is_failure( return_it() ); try is_failure( break_it() ); try is_success( error_catch() ); try std.testing.expectEqual( error.when, return_error_literal() ); try std.testing.expectEqual( @as( StructWithError, .{ .e = error.when } ), return_struct_with_error(), ); try std.testing.expectEqual( @as( StructWithErrorUnion, .{ .e = error.when } ), return_struct_with_error_union(), ); print( \\summary: \\errdefer occurs when: \\ + an error or error-union propogates outside its block \\ - unless the error is inside a struct \\catching errors prevents errors from propogating. \\to prevent errdefer, catch the error and stop its propogation. \\.. i think .. \\terminology: 'errors' -vs- 'error-unions' \\... \\so try is intuitive, does what one would expect; \\errors can be returned without triggering errdefer, by putting them inside structs... \\is the return type an error or error-union ? if so, errdefer will trigger \\i think.. \\ ); }