Error handling

Option/Result types

Option types are for types which may represent none. Result types may represent an error returned from a function.

Option types are declared by prepending ? to the type name: ?Type. Result types use !: !Type.

struct User { id int name string } struct Repo { users []User } fn (r Repo) find_user_by_id(id int) !User { for user in r.users { if user.id == id { // V automatically wraps this into a result or option type return user } } return error('User ${id} not found') } // A version of the function using an option fn (r Repo) find_user_by_id2(id int) ?User { for user in r.users { if user.id == id { return user } } return none } fn main() { repo := Repo{ users: [User{1, 'Andrew'}, User{2, 'Bob'}, User{10, 'Charles'}] } user := repo.find_user_by_id(10) or { // Option/Result types must be handled by `or` blocks println(err) return } println(user.id) // "10" println(user.name) // "Charles" user2 := repo.find_user_by_id2(10) or { return } }

V used to combine Option and Result into one type, now they are separate.

The amount of work required to "upgrade" a function to an option/result function is minimal; you have to add a ? or ! to the return type and return an error when something goes wrong.

This is the primary mechanism for error handling in V. They are still values, like in Go, but the advantage is that errors cannot be unhandled, and handling them is a lot less verbose. Unlike other languages, V does not handle exceptions with throw/try/catch blocks.

err is defined inside an or block and is set to the string message passed to the error() function.

user := repo.find_user_by_id(7) or { println(err) // "User 7 not found" return }

Handling options/results

There are four ways of handling an option/result. The first method is to propagate the error:

import net.http fn f(url string) !string { resp := http.get(url)! return resp.body }

http.get returns !http.Response. Because ! follows the call, the error will be propagated to the caller of f. When using ? after a function call producing an option, the enclosing function must return an option as well. If error propagation is used in the main() function it will panic instead, since the error cannot be propagated any further.

The body of f is essentially a condensed version of:

resp := http.get(url) or { return err } return resp.body

The second method is to break from execution early:

user := repo.find_user_by_id(7) or { return }

Here, you can either call panic() or exit(), which will stop the execution of the entire program, or use a control flow statement (return, break, continue, etc) to break from the current block.

break and continue can only be used inside a for loop.

V does not have a way to forcibly "unwrap" an option (as other languages do, for instance Rust's unwrap() or Swift's !). To do this, use or { panic(err) } instead.

The third method is to provide a default value at the end of the or block. In case of an error, that value would be assigned instead, so it must have the same type as the content of the Option being handled.

fn do_something(s string) !string { if s == 'foo' { return 'foo' } return error('invalid string') } a := do_something('foo') or { 'default' } // a will be 'foo' b := do_something('bar') or { 'default' } // b will be 'default' println(a) println(b)

The fourth method is to use if unwrapping:

import net.http if resp := http.get('https://google.com') { println(resp.body) // resp is a http.Response, not an option } else { println(err) }

Above, http.get returns a !http.Response. resp is only in scope for the first if branch. err is only in scope for the else branch.

Custom error types

V gives you the ability to define custom error types through the IError interface. The interface requires two methods: msg() string and code() int. Every type that implements these methods can be used as an error.

When defining a custom error type, it is recommended to embed the builtin Error default implementation. This provides an empty default implementation for both required methods, so you only have to implement what you really need, and may provide additional utility functions in the future.

struct PathError { Error path string } fn (err PathError) msg() string { return 'Failed to open path: ${err.path}' } fn try_open(path string) ? { return IError(PathError{ path: path }) } fn main() { try_open('/tmp') or { panic(err) } }