How do I use the Standard Library Macros in Rust? Part 1

Published June 1st, 2016

This is the second post in a series of blog posts dedicated to answering the simple question "How do I X in Rust?" After last week's post /u/Breaking-Away asked about how to use the macros available in the Standard Library. Since there are quite a few I'll breaking up the posts into at least two parts. This first post will cover the most common macros used in the language. The second post will cover ones that are available, but not used as often.

Why would we want to use Standard Library Macros?

Do you like being able to print out text to the console? If you do so using println!() then you're using a Standard Library macro! Maybe you've seen code that uses try!() in Rust? If you have it's using a Standard Library macro! These macros available to us cover common functionality that people need, but where a function will not suffice. A macro takes inputs much like a function does, however, when it's being compiled it's expanded out to actual code based off of the rules it matches and what is given as input. This makes macros pretty powerful and flexible. Even writing a macro could be it's own series of posts. Today though we're going to focus on how to use the Standard Library's macros and how you can use them effectively. In a future post I'll cover writing your own macro

The ones we'll cover in this post are:

The macros we'll cover in part two are:

You'll see the first group of macros more often then not. It should be noted that you don't need to import any of these macros as they're implicitly included in each .rs file you create unless you set that the file should not have the standard library available.

Alright let's jump in and learn us some macros!

assert!()

fn main() {
  //These will pass and the code will execute the next line
  assert!(true)
  assert!(!false)
  //This will not pass and cause the code to abort
  assert!(false)
}

assert!() takes an input that tests for some value that gives true or false. If true it'll continue code execution and if false will abort the code and let you know why. This macro is great for testing as well as for making guarantees (sometimes known as contract programming) in your code and causing it to abort if something does not go right.

assert_eq!()

fn main() {
  //These will pass and the code will execute the next line
  assert_eq!(1,1);
  assert_eq!("Hello","Hello");
  assert_eq!("there","there");
  assert_eq!(true,true);
  assert_eq!(false,false);
  //This will not pass and cause the code to abort
  assert_eq!(true,false);
}

assert_eq!() takes two parameters and compares them for equality. If they are equal it moves on with the execution of code, if not it aborts the code and lets you know what happened. Notice how you can pass any two types there? The macro is able to take a variety of different inputs unlike a function in Rust which has to`be given a type signature. This allows us to test for all kinds of different types of equalities using one macro! Can you imagine having to test for equality with a whole bunch of functions designed for specific types? It would be a real pain to deal with and that's why this macro is real nice for asserting things are equal. It's greatly used in testing or guaranteeing two things are equal in your code as it executes.

panic!()

fn main() {
  let test = String::from("Hello");

  // This is a trivial example but the code here will
  // always panic.
  if test.is_empty() {
    println!("String is empty");
  } else {
    panic!("Uh oh the String was not empty. Aborting");
  }
}

panic!() is a macro used to deallocate all resources gracefully and exit your program. Usually you'll use panic!() if you want code to abort execution in your program in a defined safe way. This is what is called if you try to unwrap a None value or an Err value for instance. You can pass it a string much like println!() meaning you can do something like this:

// Where we define e as an Error earlier in the code
panic!("This is my error: {}", e);

e will be passed into the {} as part of the string. You can have any number of {} or {:?} (the debug variant of {}). Just make sure you supply enough arguments after to fill in those spots in the string. The string passed to the macro shows up as a message after the code aborts running.

print!()

use std::io::{stdout,Write};

fn main() {
  let num = 5;
  let num2 = 9;
  print!("This is output to stdout without a newline char");
  print!("\nI told it to print a newline twice \n");
  print!("The variable num has a value of: {}", num);
  //Debug output version
  print!("\nThe variable num has a value of: {:?}", num);
  //Outputs The variable num has a value of: 5 Also num2: 9
  print!("\nThe variable num has a value of: {} Also num2: {}", num, num2);
  //Makes sure print!() items get flushed to stdout for the user to see
  stdout().flush();
}

print!() is a macro that outputs text to stdout without adding a new line char at the end of the string that it prints out. It can take any number of inputs, one for each {} or {:?}. One thing that should be noted is that print!() doesn't flush output to stdout automatically like println!() does for whatever reason. Since it doesn't if you want the output there immediately you can add a call to flush it like in the above example.

println!()

fn main() {
  let num = 5;
  let num2 = 9;
  println!("This is output to stdout with a newline char");
  println!("The variable num has a value of: {}", num);
  //Debug output version
  println!("The variable num has a value of: {:?}", num);
  //Outputs The variable num has a value of: 5 Also num2: 9
  println!("The variable num has a value of: {} Also num2: {}", num, num2);
}

println!() is a macro that outputs text to stdout and adds a new line char at the end of the string that it prints out. It can take any number of inputs, one for each {} or {:?}. Unlike print!(), println!() automatically flushes to stdout on calling it. Generally you'll see this more than print being used for reasons like this.

try!()

use std::fs::File;

fn main() {
  let _unused_result = you_made_this();
  let _unused_result = why_did_you_make_this();
}

// Trys to make a file, returns an io error if it failed
// Ok with the () return type (essentially not a type, but
// it is and that's a discussion for a different day)
fn you_made_this() -> std::io::Result<()> {
    let my_file = try!(File::create("I_Made_This.txt"));
    Ok(())
}

// Same code but way more verbose!
fn why_did_you_make_this() -> std::io::Result<()> {
   let my_file = File::create("foo.txt");

    let _result = match my_file {
        Ok(t) => t,
        Err(e) => return Err(e),
    };

    Ok(())
}

I'll be honest, up until about two weeks ago I didn't know how this worked, and I've been using Rust since 1.0 came out. That's why I'm writing this, so you don't have to fumble around with it. try!() is the match statement in the second function when expanded out. Because of this your function needs a Result return type (in this case the IO kind) matching what it's being used on to work. try!() is a nice way to unwrap results where you want the error to quit the function and allow you to handle the error explicitly. If you don't care about the error being returned or still want to do things in the function even if an error occurs don't use try!(). Otherwise feel free to use it liberally so you don't have crazy nesting trying to deal with it explicitly.

unimplemented!()

fn main() {
    // What we care about with this made up function is that it returns an
    // Option<u32> no need to actually show an implementation
    let x = fib_seq(10);
    match x {
        Some(y) = println!("{}",y),
        None    = unimplemented!(),
    }
}

In the above example I've set the None branch to say it's unimplemented. This macro lets the user continue to code and have things typecheck as if you had written things there and lets you or someone looking at the code base know that it's incomplete. However, this also means the code will panic if the macro code is reached in the flow of execution. In this case fib_seq() returned a None then this would case the code to fail. This macro is great to create stub functions or things you'll need in order to get the compiler to shut up about it and know you still need to fix it.

unreachable!()

fn main() {
    let x = squared(5);
    if x == 0 {
        println!("x was 0");
    } else if x > 0 {
        prinln!("x^2 is: {}", x);
    } else {
        unreachable!("If you got here then either math and logic is broken
        , or you should tell the compiler team");
    }
}

fn squared(n: i32) -> i32 {
    n * n
}

The above code will take a number and square it and print out either 0 or it's value. I added a trivial else statement, but if it's reached it tells us two things, either our squared function is broken or the compiler is having some problems. Either way, unreachable!() is a macro you use to let the compiler know that a certain branch of execution is impossible and it allows it to optimize it away. However, if you use it incorrectly and it's reached the code will abort. Use this if you can guarantee the code can't be reached. Otherwise you're better off providing better error handling if you're uncertain if it can be reached or not.

vec!()

fn main() {
    let x = vec![1,2,3,4,5];
    for i in x.iter() {
        println!("{}",i);
    }
    // Prints out
    // 1
    // 2
    // 3
    // 4
    // 5

    // Makes a vec of over 9000 twos.
    let y = vec![2; 9001]
}

vec![] is a macro that takes a slice (notice the [] not () used in the macro call) and constructs a vector out of the the values you pass too it. Much nicer than instantiating a Vector and pushing the individual data types into it one call to push() at a time. If you know some of the values you need inside it ahead of time this is a real nice vector to use. It's also good to make a Vec requiring repeated values a set number of times as show for the second part of the example above.

write!()

use std::io::Write;
use std::str;

fn main() {
  // This is what a byte string is actually, a slice of u8's
  // I've explicitly stated the type here for readability
  // even if the code itself didn't need it
  let mut buffer: Vec<u8> = Vec::new();
  write!(&mut buffer, "Words!").expect("Didn't write to buffer!");
  // You can also use formatted arguments like println!()

  // We had to use debug here since Vec<u8> doesn't have
  // the display trait implemented
  println!("{:?}", &buffer); // [87, 111, 114, 100, 115, 33]
  println!("{}", str::from_utf8(&buffer).unwrap()); // Words!
}

write!() is useful for creating bytestrings from input rather than having to figure out the individual numbers and pushing them into a Vec or having to do the conversion one at a time. It's like print!(), but for a buffer that you can then use to do various other things with!

writeln!()

use std::io::Write;
use std::str;

fn main() {
  // Same thing but now with new lines!
  let mut buffer: Vec<u8> = Vec::new();
  writeln!(&mut buffer, "Words!").expect("Didn't write to buffer!");
  // You can also use formatted arguments like println!()

  // Both print with the extra new line but only in the first one do you
  // see it manifest as a visible number (it's the 10)
  println!("{:?}", &buffer); // [87, 111, 114, 100, 115, 33, 10]
  println!("{}", str::from_utf8(&buffer).unwrap()); // Words!
}

writeln!() is useful for creating bytestrings from input rather than having to figure out the individual numbers and pushing them into a Vec or having to do the conversion one at a time and it automagically adds a newline for you! It's like println!(), but for a buffer that you can then use to do various other things with!

Conclusion

Here's the quick rundown of all the macros we just covered:

These are the most common ones you'll see used in Rust code, but you might see the other ones that I'll cover in the next post out in the wild. This should give you a good grasp of what is available to you as a user of the language and a taste of how powerful macros can be.

If you have a burning question that you want answered but just can't seem to find the answer you need, shoot me an email at mgattozzi@gmail.com and I'll write a post to answer your question! No question is stupid. Chances are if you're having trouble with it so is someone else so ask away!

If you're a more experienced Rust user and want to help with the wording or give suggestions send me a PR or reach out to me and I'll include changes if they're good. You'll also get credit for your contributions of course!