Defer in Crystal

# Defer is used to ensure that a function call is performed later in a program's
# execution, usually for purposes of cleanup. In Crystal, we use `ensure` blocks
# for similar purposes.

require "file"

# Suppose we wanted to create a file, write to it,
# and then close when we're done. Here's how we could
# do that with `ensure`.

def main
  # Immediately after getting a file object with
  # `File.new`, we put the closing of that file
  # in an `ensure` block. This will be executed at the end
  # of the enclosing function (`main`), after
  # `write_file` has finished.
  f = create_file("/tmp/defer.txt")
  begin
    write_file(f)
  ensure
    close_file(f)
  end
end

def create_file(p : String) : File
  puts "creating"
  File.new(p, "w")
rescue ex
  puts "Error: #{ex.message}"
  exit(1)
end

def write_file(f : File)
  puts "writing"
  f.puts "data"
end

# It's important to check for errors when closing a
# file, even in an ensure block.
def close_file(f : File)
  puts "closing"
  f.close
rescue ex
  STDERR.puts "error: #{ex.message}"
  exit(1)
end

main

Running the program confirms that the file is closed after being written.

$ crystal run defer.cr
creating
writing
closing

In Crystal, we use ensure blocks to achieve similar functionality to Go’s defer. The ensure block guarantees that the code within it will be executed when the enclosing method exits, regardless of whether an exception was raised or not.

The structure of the program remains similar, but we’ve made a few Crystal-specific adjustments:

  1. We use require "file" instead of importing specific packages.
  2. We define a main method and call it at the end of the file, as Crystal doesn’t have a special main function.
  3. Instead of using defer, we use a begin/ensure block to ensure the file is closed.
  4. We use Crystal’s exception handling (rescue) instead of explicitly checking for errors.
  5. We specify types for method parameters and return values.
  6. We use File.new instead of os.Create to create a file.
  7. We use STDERR.puts instead of fmt.Fprintf(os.Stderr, ...) for error output.

This Crystal code provides the same functionality as the original Go code, ensuring that resources are properly cleaned up using ensure blocks.