Failures
? for propagation
Section titled “? for propagation”On a Result, ? unwraps Ok on success, and force-returns Err on failure:
fn read_config(path: string) -> Result<Config, error> { let bytes = os.ReadFile(path)?on success, bytes is the value inside Ok
on failure, read_config returns Err parse_config(bytes)}Equivalent without ?:
fn read_config(path: string) -> Result<Config, error> { let bytes = match os.ReadFile(path) { Ok(contents) => contents, Err(failure) => return Err(failure), } parse_config(bytes)}On an Option, ? unwraps Some when present, and force-returns None when absent:
fn get_name(id: int) -> Option<string> { let user = users.get(id)?when present, user is the value inside Some
when absent, get_name returns None Some(user.name)}Equivalent without ?:
fn get_name(id: int) -> Option<string> { let user = match users.get(id) { Some(found) => found, None => return None, } Some(user.name)}Attaching context to errors
Section titled “Attaching context to errors”? propagates an error unchanged. To record where it passed through, wrap_err() prepends a message to the error while keeping the original as the cause:
fn read_config(path: string) -> Result<Config, error> { let bytes = os.ReadFile(path).wrap_err("reading config file")? parse_config(bytes)}An Ok passes through untouched. If os.ReadFile fails, ? propagates the wrapped error, which reads reading config file: open /etc/app.conf: no such file or directory. The original is preserved, so errors.Is and errors.As still match against it.
try blocks
Section titled “try blocks”A try block groups fallible calls and evaluates to a Result. Inside it, ? returns from the block rather than from the function:
fn load_config() -> Config { let result = try { let path = env.get("CONFIG_PATH")?failure propagates to result let file = fs.read(path)? parse_toml(file)? }
match result { Ok(config) => config, Err(_) => Config.default(), }}Defining your own errors
Section titled “Defining your own errors”Any type with an Error() method can be returned as an error:
struct ValidationError { field: string, message: string,}
impl ValidationError { fn Error(self) -> string { f"{self.field}: {self.message}" }}
fn validate(input: Input) -> Result<Input, ValidationError> { if input.name == "" { return Err(ValidationError { field: "name", message: "required" }) } Ok(input)}It then propagates with ? into any function returning error:
fn process(input: Input) -> Result<Input, error> { let valid = validate(input)?ValidationError satisfies error Ok(valid)}Partial results
Section titled “Partial results”Where a Go function returns a value alongside an error, the result is a Partial<T, E>. Handle its three cases with match:
match reader.Read(buf) { Partial.Ok(n) => process(buf[..n]), Partial.Err(err) => return Err(err), Partial.Both(n, err) => {read bytes, then reached EOF process(buf[..n]) if errors.Is(err, io.EOF) { return Ok(()) } return Err(err) },}An error is an interface value, so == is refused on it. Compare with errors.Is, which also matches an error wrapped by wrap_err().
No unwrap()
Section titled “No unwrap()”Lisette omits Rust’s unwrap(). To unwrap a value, use:
?to propagate failurematchto handle every caselet elsefor early exitunwrap_or()with a default
Panic recovery
Section titled “Panic recovery”To catch panics at runtime, Go uses recover() in a deferred anonymous function:
go func() { defer func() { if r := recover(); r != nil { log.Println(r) } }() handleConnection(conn)}()Lisette’s recover block serves the same purpose, yielding a PanicValue that carries what the panic passed:
let result = recover { handle_connection(conn)}
if let Err(pv) = result { log.Println(pv.message())}