Blog Series: From TypeScript to Rust & Go — Day 3: Mastering Functions and Error Handling
Day 3: Writing Reusable Functions and Handling Errors in Rust and Go
Introduction
Welcome to Day 3 of our twice-weekly journey into Rust and Go! Today, we’ll explore functions and error handling, which are essential tools for writing clean, maintainable, and robust backend code.
By the end of this post, you’ll be able to:
- Write reusable functions in both Rust and Go.
- Handle errors gracefully.
- Identify similarities with TypeScript’s function syntax for a smoother transition.
Let’s dive in!
1. Functions in Rust and Go
Functions help us avoid repeating code by encapsulating logic into reusable building blocks.
Functions in Rust
Rust functions are defined using the fn keyword.
- Parameters and return types must have explicit types.
- Return values are defined with the -> symbol.
Example:
fn greet(name: &str) -> String { format!("Hello, {}!", name)}fn main() { let message = greet("Alice"); println!("{}", message);} Key Concepts:
- Type Annotations: Every parameter (name: &str) and return value (-> String) must specify a type.
- Ownership and Borrowing: Notice &str—this indicates a string slice, used for borrowing data instead of taking ownership.
Functions in Go
In Go, functions use the func keyword.
- Types are required for parameters and return values, but they appear after the variable name.
- Go supports multiple return values.
Example:
package mainimport "fmt"func greet(name string) string { return fmt.Sprintf("Hello, %s!", name)}func main() { message := greet("Alice") fmt.Println(message)} Key Concepts:
- Type Placement: Unlike Rust, Go places the type after the parameter name (name string).
- fmt.Sprintf: Similar to Rust’s format!, it allows formatted string creation.
2. Handling Errors
Error handling is critical for building robust applications. Both Rust and Go emphasize catching and managing errors early.
Error Handling in Rust
Rust uses the Result enum for error handling:
- Result is either Ok (success) or Err (error).
- You must explicitly handle errors using patterns like match or the ? operator.
Example: File Reading in Rust
use std::fs;fn read_file(filename: &str) -> Result { fs::read_to_string(filename)}fn main() { match read_file("example.txt") { Ok(content) => println!("File content: {}", content), Err(e) => eprintln!("Error reading file: {}", e), }} Key Concepts:
- The match block checks for Ok or Err.
- The ? operator can propagate errors upwards for simpler handling.
Simplified Version with? Operator:
fn main() -> Result { let content = fs::read_to_string("example.txt")?; println!("File content: {}", content); Ok(())} Error Handling in Go
Go takes a different approach with explicit error values:
- Functions often return two values: a result and an error.
- Developers handle errors immediately after calling a function.
Example: File Reading in Go
package mainimport ( "fmt" "io/ioutil")func readFile(filename string) (string, error) { content, err := ioutil.ReadFile(filename) if err != nil { return "", err } return string(content), nil}func main() { content, err := readFile("example.txt") if err != nil { fmt.Println("Error reading file:", err) return } fmt.Println("File content:", content)} Key Concepts:
- The error type is built into Go and used widely.
- Handling errors immediately helps maintain clarity.
Tip for TypeScript Developers: Think of Go’s error handling as similar to returning Promise objects and manually checking the .catch() clause. Rust’s Result is more like TypeScript’s Either monads from functional programming libraries.
4. Hands-On Practice: Building a Calculator
Let’s create a simple calculator program in both Rust and Go. The calculator will support addition, subtraction, multiplication, and division, and handle invalid inputs gracefully.
Rust Code
fn calculate(a: f64, b: f64, operator: char) -> Result { match operator { '+' => Ok(a + b), '-' => Ok(a - b), '*' => Ok(a * b), '/' => { if b == 0.0 { Err("Cannot divide by zero".to_string()) } else { Ok(a / b) } } _ => Err("Invalid operator".to_string()), }}fn main() { match calculate(10.0, 2.0, '+') { Ok(result) => println!("Result: {}", result), Err(e) => println!("Error: {}", e), }} Go Code
package mainimport ( "errors" "fmt")func calculate(a, b float64, operator string) (float64, error) { switch operator { case "+": return a + b, nil case "-": return a - b, nil case "*": return a * b, nil case "/": if b == 0 { return 0, errors.New("cannot divide by zero") } return a / b, nil default: return 0, errors.New("invalid operator") }}func main() { result, err := calculate(10, 2, "+") if err != nil { fmt.Println("Error:", err) } else { fmt.Println("Result:", result) }} 5. Day 3 Homework
- Basic Function Practice:
- Write a function in Rust and Go that takes a number as input and returns whether it’s even or odd.
- Error Handling Practice: Modify today’s calculator to take input from the user and handle invalid inputs (e.g., non-numeric values).
- Advanced Challenge: Write a Rust and Go program that reads a file, reverses its content, and writes it back to a new file.
6. What’s Next?
In Day 4, we’ll learn about data structures in Rust and Go, including arrays, slices, and maps. These will be critical as we start building APIs and managing data.
Stay tuned for the next post, and don’t forget to share your progress or questions in the comments!

