Did you know you can just whip out curly-braces all over the place in Rust and Go?

a := 1
a = 2

{ b := 1 }
b := 2
let mut a = 1;
a = 2;

{ let b = 1; };
let b = 2;

let c = {
  let mut d = 1;
  d = 2;
  d
};

But why on Earth would you even want to do this?


Creating “Construction-Zones”

When you’re creating a variable that demands multiple initialization steps, it’s tider to tuck the details into a block:

// before
let person = Person(id);
person.authenticate(token).await;
let raw_email = person.fetchEmail().await;
let email = Email.parse(email);
// after
let email = {
  let person = Person(id);
  person.authenticate(token).await;
  let raw_email = person.fetchEmail().await;
  Email.parse(email)
}

In this example, it’s obvious that the “end-goal” is email. At a quick-glance, you can see which variables are important, and which are intermediate ephemera.

Containing Side-Effects and Error-Handling

Every golang programmer knows the pain of juggling result and err variables. With blocks, you can tidily handle each err and other variables without worrying about weird assignment problems with := and =:

id := 123
firstName := "John"
lastName := "Smith"

{
  result, err := db.Exec(
    `UPDATE usr SET first_name = $2 WHERE id = $1`,
    id,
    firstName,
  )
  if result.RowsAffected() = 0 || err != nil {
    return err
  }
}

{
  result, err := db.Exec(
    `UPDATE usr SET last_name = $2 WHERE id = $1`,
    id,
    lastName,
  )
  if result.RowsAffected() = 0 || err != nil {
    return err
  }
}

Restricting Variable Lifetimes

I like creating tidy “workspaces” for each group of intermediate variables:

// define vars
// do stuff with vars
{ 
  // define intermediate vars
  // manipulate top-level vars
}
// do more stuff with vars
{ 
  // define intermediate vars
  // manipulate top-level vars
}
// do even more stuff with vars

For example:

email := "[email protected]"

usr := Usr{}
{
  var usrId int
  {
    err := db.QueryRow(
      `SELECT usr_id FROM usr_email WHERE email = $1`,
      email,
    ).Scan(&usrId)
    if err != nil {
      return err
    }
  }

  {
    err := db.QueryRow(
      `SELECT * FROM usr WHERE id = $1`,
      usrId,
    ).Scan(&usr)
    if err != nil {
      return err
    }
  }
}

In this very contrived example, you can easily see the purpose of everything just by glancing at the general structure:

  • email is at the beginning with an explicit value, so it’s a given input
  • usr is declared empty, so the block directly beneath it will define it
  • usrId is declared empty, so the block directly beneath it will define it
  • usr and email are the only variables that we need to care about after this section

Conclusion

Use {} blocks to prevent your variables from leaking everywhere.

Your friends and coworkers will thank you.

Read More