闽公网安备 35020302035485号
// $GOROOT/src/builtin/builtin.go
// 堆代码 duidaima.com
// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {
Error() string
}
显式返回值package main
import (
"fmt"
"os"
)
func readFileContent(filename string) (string, error) {
data, err := os.ReadFile(filename) // ReadFile returns ([]byte, error)
if err != nil {
// If an error occurs (e.g., file not found), return it
return "", fmt.Errorf("failed to read file %s: %w", filename, err) // Wrap the original error
}
return string(data), nil // Success, return data and nil error
}
func main() {
content, err := readFileContent("my_file.txt")
if err != nil {
// The iconic check
fmt.Fprintf(os.Stderr, "Error reading file: %v\n", err)
// Here you would typically handle the error (log, return, etc.)
return
}
fmt.Println("File content:", content)
// Slightly shorter form for functions returning only error (like Close)
// Use dummy file creation/opening for example that runs
f, createErr := os.Create("temp_file.txt")
if createErr != nil {
fmt.Fprintf(os.Stderr, "Error creating file: %v\n", createErr)
return
}
if f != nil {
// Ensure file is closed even if writes fail later (using defer is better practice)
defer f.Close()
defer os.Remove("temp_file.txt") // Clean up the dummy file
// Example usage...
_, _ = f.WriteString("hello")
// Now explicitly check close error if needed at the end of func,
// though defer handles the call itself.
// For demonstration of the if err := ... style on Close:
// (Note: defer already schedules the close, this is just for syntax demo)
// closerFunc := func() error { return f.Close() } // Wrap Close if needed
// if err := f.Close(); err != nil { // Potential re-close if not careful with defer
// fmt.Fprintf(os.Stderr, "Error closing file: %v\n", err)
// }
// A more practical place for this pattern might be a non-deferred close.
}
}
示例中,对每一处返回错误的地方都做了显式检查,这保证了错误不会被轻易忽略,控制流清晰可见,但也导致了代码冗长。上面代码因my_file.txt文件不存在,会输出“Error reading file: failed to read file my_file.txt: open my_file.txt: no such file or directory”并退出。package main
import (
"errors"
"fmt"
"os"
"time"
)
// Custom error type
type OperationError struct {
Op string
Err error // Underlying error
Timestamp time.Time
}
// Implement the error interface
func (e *OperationError) Error() string {
return fmt.Sprintf("[%s] operation %s failed: %v", e.Timestamp.Format(time.RFC3339), e.Op, e.Err)
}
// Function that might return our custom error
func performCriticalOperation() error {
// Simulate a failure
err := errors.New("connection refused")
return &OperationError{
Op: "connect_database",
Err: err,
Timestamp: time.Now(),
}
}
// (main function using this will be shown in the next point)
错误检查// (Continuing from previous snippet within the same package)
func main() {
err := performCriticalOperation()
if err != nil {
fmt.Fprintf(os.Stderr, "Operation failed: %v\n", err) // Prints the formatted custom error
// Example: Check if the underlying error is a specific known error
// Note: Standard errors package doesn't export connection refused directly,
// this is conceptual. Real check might involve string matching or syscall types.
// if errors.Is(err, someSpecificNetworkError) {
// fmt.Println("It was specifically a network error")
// }
// Check if the error is of our custom type and extract it
var opErr *OperationError
if errors.As(err, &opErr) {
fmt.Fprintf(os.Stderr, " Operation details: Op=%s, Time=%s, UnderlyingErr=%v\n",
opErr.Op, opErr.Timestamp.Format(time.Kitchen), opErr.Err)
// Can now use opErr.Op, opErr.Timestamp etc. for specific handling
}
}
}
该博主认为,Go的方式虽然有点“乏味”和冗长,但非常直接 (straightforward),且自定义错误携带丰富上下文的能力是一大优势,使得错误本身更具“可编程性”。const std = @import("std");
// Define possible errors for our function
const MyError = error{
InvalidInput,
ConnectionFailed,
SomethingElse,
};
// Function signature indicating it can return MyError or u32
fn doSomething(input: u32) MyError!u32 {
if (input == 0) {
return MyError.InvalidInput; // Return a specific error
}
if (input > 100) {
return MyError.ConnectionFailed; // Return another error
}
// Simulate success
return input * 2; // Return the successful result (u32)
}
// Example usage needs a main function
// pub fn main() !void { // Example main, !void indicates main can return error
// const result = try doSomething(50);
// std.debug.print("Result: {}\n", .{result});
// }
强制处理const std = @import("std");
const MyError = error{InvalidInput, ConnectionFailed}; // Simplified error set
// Function definition (same as above)
fn doSomething(input: u32) MyError!u32 {
if (input == 0) return MyError.InvalidInput;
if (input > 100) return MyError.ConnectionFailed;
return input * 2;
}
// 堆代码 duidaima.com
// This function also returns MyError or u32
fn processData(input: u32) MyError!u32 {
// If doSomething returns an error, 'try' immediately propagates
// that error from processData. Otherwise, result holds the u32 value.
const result = try doSomething(input);
// ... further processing on result ...
std.debug.print("Intermediate result in processData: {}\n", .{result});
return result + 1;
}
pub fn main() !void { // Main now can return errors (due to try)
const finalResult = try processData(50); // Propagate error from processData
std.debug.print("Final result: {}\n", .{finalResult});
// Example of triggering an error propagation
// Uncommenting the line below will cause main to return InvalidInput
// _ = try processData(0);
}
注:Zig中的try可不同于Java等支持try-catch等错误处理机制中的try。Zig 的 try 用于传播错误,而 Java 的 try-catch 用于捕获和处理异常。const std = @import("std");
const MyError = error{InvalidInput, ConnectionFailed};
fn doSomething(input: u32) MyError!u32 { /* ... */ if (input == 0) return MyError.InvalidInput; return input * 2; }
pub fn main() void { // Main does not return errors itself
const result = doSomething(0) catch |err| {
// Error occurred, execution enters the catch block
std.debug.print("Caught error: {s}\n", .{@errorName(err)}); // Prints "Caught error: InvalidInput"
// Handle the error, maybe exit or log differently
// For this example, we just print and return from main
return; // Exit main gracefully
};
// This line only executes if doSomething succeeded
// If input was non-zero, this would print.
std.debug.print("Success! Result: {}\n", .{result});
}
与回退值结合 (catch fallbackValue),在出错时提供一个默认的成功值const std = @import("std");
const MyError = error{InvalidInput, ConnectionFailed};
fn doSomething(input: u32) MyError!u32 { /* ... */ if (input == 0) return MyError.InvalidInput; return input * 2; }
pub fn main() void {
// If doSomething fails (input is 0), result will be assigned 999
const result = doSomething(0) catch 999;
std.debug.print("Result (with fallback): {}\n", .{result}); // Prints 999
const success_result = doSomething(10) catch 999;
std.debug.print("Result (with fallback, success case): {}\n", .{success_result}); // Prints 20
}
与命名块结合const std = @import("std");
const MyError = error{
FileNotFound,
InvalidData,
};
fn readDataFromFile(filename: []const u8) MyError![]const u8 {
// 模拟读取文件,如果文件名是 "error.txt" 则返回错误
if (std.mem.eql(u8, filename, "error.txt")) {
return MyError.FileNotFound;
}
// 模拟读取成功
const data: []const u8 = "Some valid data";
return data;
}
fn handleReadFile(filename: []const u8) []const u8 {
return readDataFromFile(filename) catch |err| {
std.debug.print("Error reading file: {any}\n", .{err});
std.debug.print("Using default data\n", .{});
return "Default data";
};
}
pub fn main() !void {
const filename = "data.txt";
const errorFilename = "error.txt";
const data = handleReadFile(filename);
std.debug.print("Data: {s}\n", .{data});
const errorData = handleReadFile(errorFilename);
std.debug.print("Error Data: {s}\n", .{errorData});
}
注:对于Gopher而言,是不是开始感觉有些复杂了:)。const std = @import("std");
const MyError = error{InvalidInput, ConnectionFailed, SomethingElse};
fn doSomething(input: u32) MyError!u32 {
if (input == 0) return MyError.InvalidInput;
if (input > 100) return MyError.ConnectionFailed;
if (input == 55) return MyError.SomethingElse; // Add another error case
return input * 2;
}
pub fn main() void {
// Test Case 1: Success
if (doSomething(10)) |successValue| {
std.debug.print("Success via if/else (input 10): {}\n", .{successValue}); // Prints 20
} else |err| { std.debug.print("Error (input 10): {s}\n", .{@errorName(err)}); }
// Test Case 2: ConnectionFailed Error
if (doSomething(101)) |successValue| {
std.debug.print("Success via if/else (input 101): {}\n", .{successValue});
} else |err| {
std.debug.print("Error via if/else (input 101): ", .{});
switch (err) {
MyError.InvalidInput => std.debug.print("Invalid Input\n", .{}),
MyError.ConnectionFailed => std.debug.print("Connection Failed\n", .{}), // This branch runs
else => std.debug.print("Unknown error\n", .{}),
}
}
// Test Case 3: SomethingElse Error (falls into else)
if (doSomething(55)) |successValue| {
std.debug.print("Success via if/else (input 55): {}\n", .{successValue});
} else |err| {
std.debug.print("Error via if/else (input 55): ", .{});
switch (err) {
MyError.InvalidInput => std.debug.print("Invalid Input\n", .{}),
MyError.ConnectionFailed => std.debug.print("Connection Failed\n", .{}),
else => std.debug.print("Unknown error ({s})\n", .{@errorName(err)}), // This branch runs
}
}
}
catch unreachableconst std = @import("std");
// Assume this function logically should never fail based on guarantees elsewhere
fn doSomethingThatShouldNeverFail() !u32 {
// For demo, make it fail sometimes
// if (std.time.timestamp() % 2 == 0) return error.UnexpectedFailure;
return 42;
}
pub fn main() void {
// If doSomethingThatShouldNeverFail returns an error, this will panic.
// Useful when an error indicates a programming bug.
const result = doSomethingThatShouldNeverFail() catch unreachable;
std.debug.print("Result (unreachable case): {}\n", .{result});
// To see it panic, you'd need doSomethingThatShouldNeverFail to actually return an error.
}
该博主认为,Zig 的错误处理方式功能更丰富、更强大、也**更简洁 (concise)**。try 关键字尤其强大,极大地减少了错误传播的样板代码。| 特性 | Go | Zig |
|---|---|---|
| 核心哲学 | 错误即值 | 错误即值 |
| 实现机制 | error 接口, (T, error) 返回值 | 错误联合类型 (!T), 特殊错误枚举 |
| 处理方式 | 显式 if err != nil 检查 | try, catch (多种形式), if/else catch |
| 强制性 | 靠约定和 Linter | 编译器强制 |
| 简洁性 | 某些时候略冗长 | 简洁 (try) |
| 上下文 | 通过自定义类型携带丰富上下文 | 错误值本身通常不带上下文 (目前) |
| 编程性 | 高 (errors.Is/As, 自定义类型) | 相对较低 (主要靠错误类型区分) |
| 易理解性 | 非常直接 | 语法糖需学习,但逻辑算是清晰 |
Zig 则选择了更精巧、更简洁且由编译器强制保证的路径。 它通过强大的语法糖显著减少了样板代码,提升了编写体验,但在错误本身携带上下文信息方面目前有所欠缺。