Introduction: "Mute" println! — A Familiar Pain for Every Rust Beginner
\n\nYou are writing your first unit test in Rust. Everything is logical: you create a structure, implement a method, and then — the triumphant moment of verification. You insert println! into the test to make sure the data is correct. You run cargo test... And you only see a dry line: "test result: ok. 1 passed; 0 failed". No "Hello, world!", no intermediate values. Your println! has simply disappeared.
Sound familiar? Welcome to the club. This question on Stack Overflow has gathered thousands of views, and it is a real stumbling block for everyone learning testing in Rust. In this article, we won't just say "use --nocapture". We will break down why this happens, how output capture works in the test runner, and how to properly debug code without violating the principles of unit testing.
Why Does Rust "Muzzle" println! in Tests?
\n\nIt's not a bug or an evil intent by the language creators. It is a deliberate architectural decision. When you run cargo test, Rust makes tests clean and predictable. Imagine you have 500 tests, and each one outputs 10 lines of debugging to stdout. Your terminal would turn into a mess, and more importantly, you wouldn't be able to distinguish real errors from debugging noise.
By default, the test runner (the test library) intercepts all standard output (std::io::stdout()) and standard error output (std::io::stderr()). It does this at the level of system descriptors. println! writes specifically to stdout, so its output goes into the runner's "black box". The output is only shown if the test fails — so you can see what went wrong.
What Does This Look Like in Code?
\n\nLet's look at a simple example that reproduces the problem:
\n\n// src/lib.rs\npub fn add_two(a: i32) -> i32 {\n a + 2\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn test_add_two() {\n println!("I am executing, but you can't see me!");\n let result = add_two(2);\n println!("Result: {}", result);\n assert_eq!(result, 4);\n }\n}\n\nWe run:
\n\n$ cargo test\n Compiling my_project v0.1.0\n Finished test [unoptimized + debuginfo] target(s) in 0.5s\n Running unittests src/lib.rs (target/debug/deps/my_project-abc123)\n\nrunning 1 test\ntest tests::test_add_two ... ok\n\ntest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s\n\nAs you can see, none of the println! messages appeared. The test passed successfully, and the runner decided there was nothing to show you.
How to "Wake Up" println!? Methods and Pitfalls
\n\nThere are three main approaches, and each is good for its own situation.
\n\nMethod 1: The --nocapture Flag (Simplest and Most Popular)
\n\nThis is what you will find in the first answer on Stack Overflow. The --nocapture flag disables output capture for all tests in the current run.
$ cargo test -- --nocapture\n\nNote the double dash --. It separates arguments for cargo and for the test binary itself. Everything after -- is passed to the test runner.
Now the output will be:
\n\n$ cargo test -- --nocapture\n Compiling my_project v0.1.0\n Finished test [unoptimized + debuginfo] target(s) in 0.5s\n Running unittests src/lib.rs (target/debug/deps/my_project-abc123)\n\nrunning 1 test\nI am executing, but you can't see me!\nResult: 4\ntest tests::test_add_two ... ok\n\ntest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered