What is this ```assert!()``` I keep seeing?

I'm continuing to work through the Rust book and as I go I've been scrolling through and taking notes on the functions and methods available in the various standard modules. For instance, this morning I was looking at the char module, char - Rust, and the is_control() method in particular. I see assert!() everywhere in this documentation and have no idea what that function (or macro??) does or what it means. Could someone enlighten me, please? Thanks.

It's for making sure a condition is true. If the given value is falsy, then it panics. Also, it's a macro, so you can find it in the std library's "Macros" section.

The idea is: When a certain condition is supposed to always be true, you can write that condition down as an assert!(), so that the program will actually verify the condition at runtime. If the check ever fails, your program will panic (abort), instead of going on with a "bad" state! It's kind of an emergency stop.

You will often see assert!()s in documentation/examples, to illustrate what the "expected" outcome is.

2 Likes

Another reason why we see so many assertions in rust documentation compared to other languages, it may be explained because cargo test actually test code snippets in documentation as well.

7 Likes

You can read about the general concept here.

1 Like

Also be aware of the assert_eq! and assert_ne! macros. They’re like assert!(x == y) or assert!(x != y), but they helpfully print out the values that weren’t/were equal for easier debugging.

Also be aware of the debug_assert family of macros. They’re like the asserts but they only get run in debug profile, not in release. Use this only if the assert is not part of your crate’s public API.

2 Likes

In regard to examples, the following two are often (de-facto) identical:

let x = 1 + 2;
// `x` is 3 now!

And:

let x = 1 + 2;
assert!(x == 3);

Because the compiler knows that x will be 3, the assert! is sort-of useless here and will do nothing. However, using assert! instead of writing a comment has the following advantage:

If we made a mistake with our assertion (or later change something which makes the assertion wrong), then the program will panic when the condition turns out to be false, instead of keep running and doing difficult-to-debug things.

Sometimes, we also put an assertion on things a caller must ensure, e.g. assert!(vec.len() >= 2) if our function requires vec to contain at least two elements. This is more handy than writing the following, which is effectively the same:

if vec.len() < 2 {
    panic!("vec.len() is smaller than 2");
}
1 Like

There’s more information in the book in the chapter about testing.

Writing Automated Tests -> How to Write Tests -> Checking Results with the assert! Macro

As @ndusart already mentioned, the reason why assert! (and assert_eq!), which is very commonly used for writing tests, appears in documentation a lot is that in Rust documentation, every example is also a test! With this in mind, using assert!(…) instead of a comment will more clearly point out guarantees to the reader, since words are imprecise and comments become dated, but an assert!(…some expression…) has a clear meaning and will always stay up to date (because it’s (typically) being automatically tested when the library is released).

4 Likes

Ok. Thanks for your comments and they are helping me understand a little why the assert!() macros are around. Still, I find its usage in the documentation a little annoying. Far too often, when I'm looking at some method I might want to use in the future, I scroll down to see an example of how it's used and instead of an example, I see an assert!() statement. Doesn't help at all.

Still, I am beginning to see how it could be useful and I'll start looking for an opportunity to use it. That should help me get a handle on how it works and how it should be used.

Thank you, everyone, for your comments! :grinning:

I personally really like the usage of asserts in the docs. I frequently find myself looking at code examples in other languages that show you how to call the function but not how to actually use it to accomplish a task.

Asserting expected outputs gives a more complete picture of how the function is supposed to behave, as opposed to just showing you what arguments you can pass.

3 Likes

The other thing to be generally aware of is that the optimizer can often spot that conditions must be true because it's already seen code that checks that condition. The classic example is bounds checking:

// Two bounds checks:
// * Does list[0] exist?
// * Does list[1] exist?
fn add_first_two(list: &[f64]) -> f64 {
   list[0] + list[1]
}
// One bounds check:
// * Does list[1] exist?
// * If list[1] exists, then list[0] must exist so no check needed
fn add_first_two(list: &[f64]) -> f64 {
   list[1] + list[0]
}
// No bounds checks, just the assert
// * if list.len() >= 2, then list[0] must exist
// * if list.len() >= 2, then list[1] must exist
fn add_first_two(list: &[f64]) -> f64 {
   assert!(list.len() >= 2, "not enough elements");
   list[0] + list[1]
}

but the same pattern of the optimizer identifying dead code thanks to a well-placed assert! can occur elsewhere.

4 Likes

Usually, it's used not instead of the example, but along with the example, to show not only the usage, but the result too. If this is somewhere not the case - especially in standard library docs - it might be worth an issue for clarification.

4 Likes

Assertions in examples are part of the example. They show you what the result of using a particular API should be. Specifically what do you find unhelpful about that? Maybe you are misunderstanding the documentation.

1 Like

assert!() => its just a macro to check a condition if it's true or not (it panics). You can add your custom message for panic.
e.g. assert!(1==2,"Hello!, Its not working");

There are other flavors of this too assert_ne!() and assert_eq!(),
e.g. assert_eq!(1,2,"Hello!, Its not working");
e.g. assert_ne!(1,2,"Will not print this");

  • You can try these things quickly using rust play site

https://play.rust-lang.org/

1 Like

@farnz Your examples really helped. Thanks.

So, I've been trying to get a handle on using hashmaps, and have been doing some experimenting. In the process I wrote a short function to create, fill, and return a hashmap. The function takes two passed vectors, one containing the data for keys and the other holding the matching values, to fill the map. Of course, the two vectors must be the same length, so I used an assert!() to check for that and panic the program if the vectors don't match. Here's a snippet:

fn fill_map(rooster: Vec<u32>, hen: Vec<u32>) -> HashMap<u32, u32> {
    assert!(
        rooster.len() == hen.len(),
        "The two vectors are not equal in length."
    );

The function seems to work the way I intended, but am I using assert!() correctly?

1 Like

Looks good to me. Idiomatically you'd maybe use assert_eq!. And by convention, error messages are typically lowercase without trailing punctuation (see docs):

Error messages are typically concise lowercase sentences without trailing punctuation


One other remark: In this particular case, you might want the input types to be &[u32] instead of Vec<u32> because copying each u32 would be very cheap.

2 Likes

I'm guessing that I'm not understanding something here. Since I pass the two vectors to the function because it needs the entire vector so that it can fill the hashmap. If I use &[u32] would the entire vector be passed over? I had already decided to use &Vec<u32> so that I wouldn't be passing ownership, but I'm not sure how your suggestion works. Could you elaborate on it a bit? Thanks.

I just changed my error message to match up with the convention, but I'm not sure I like it. I'm a retired teacher and proper capitalization and punctuation are important to me. I also find the "conciseness" of most error messages to be an obstacle rather than a help. I need more information than most error messages give me and it seems that longer explanations would be helpful to most people, not just me.