Robust Error Handling & Optionals
Ube has no runtime exceptions and no unwieldy try-catch blocks. Failure is represented explicitly in the type system through Result types (T ! E) and Optionals (T?).
1. Result Types (T ! E)
A function that can fail specifies both its success value type T and its error value type E separated by an exclamation mark: T ! E. Use the error(...) built-in to return an error variant:
fn parse_server_port(input: i32) -> i32 ! string {
if input < 1024 {
return error("port numbers below 1024 are reserved")
}
if input > 65535 {
return error("port number exceeds maximum allowed (65535)")
}
return input // Success value returned directly
}
fn main() {
let result = parse_server_port(8080)
// Results must be handled via '?' or 'or_else'
}
2. The ? Propagation Operator
When calling a function that returns a Result within another function that also returns a Result, append ? to immediately return the error upon failure:
struct Config {
port: i32
}
fn initialize_config(raw_port: i32) -> Config ! string {
// If parse_server_port returns an error, execution returns early with that error
let valid_port = parse_server_port(raw_port)?
return Config {
port: valid_port
}
}
fn main() {
let cfg = initialize_config(3000) or_else Config { port: 8080 }
println(f"Server configured on port {cfg.port}")
}
3. Fallback Defaults with or_else
The or_else operator unwraps a Result, extracting the success value if valid, or falling back to a provided default expression if an error occurred:
fn main() {
// Safe fallback value if parsing fails
let fallback_port = parse_server_port(-1) or_else 80
println(f"Resolved port: {fallback_port}") // 80
let valid = parse_server_port(9000) or_else 80
println(f"Resolved port: {valid}") // 9000
}
4. Optionals (T?) and null
When a value may be absent without an explanatory error message, use an Optional type written as T?. Optionals can take any value of type T or literal null:
struct User {
id: i32,
username: string
}
fn query_user(id: i32) -> User? {
if id == 42 {
return User { id: 42, username: "John" }
}
return null
}
5. Pattern Unwrapping with if let
To safely unpack an optional value, use the if let construct. It binds the inner value inside the true branch:
fn main() {
let lookup = query_user(42)
if let user = lookup {
println(f"Found user #{user.id}: {user.username}")
} else {
println("User was not found in database.")
}
}
6. Stream & Channel Loops with while let
The while let statement combines looping and optional unwrapping. It continuously loops until an optional returns null or a channel closes:
fn drain_queue(ch: Chan[i32]) {
// ch.recv() returns i32? (null when channel is empty and closed)
while let item = ch.recv() {
println(f"Processing queued item: {item}")
}
println("Queue drained cleanly.")
}