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!