# quickcheck for Rust: property based testing with shrinking

> BurntSushi/quickcheck generates random inputs for a property function and shrinks failures to smaller counter-examples. It suits Rust projects that want a small dev-dependency, not teams that need fine-grained control over generation.

**BurntSushi/quickcheck** — Automated property based testing for Rust (with shrinking).

- Repository: https://github.com/BurntSushi/quickcheck
- Stars: 2,797 · Forks: 165
- Language: Rust
- License: Unlicense
- Published: 2026-09-28 · Updated: 2026-09-28 · Language: en
- Canonical page: https://hysenlabs.com/projects/burntsushi-quickcheck

## What quickcheck solves, and who it is for

Example-based tests check the cases you thought of. quickcheck checks the cases you did not. The README describes it as a way to do property based testing using randomly generated input: you supply a property function, and the crate generates inputs, calls the property for each set, and when something fails it shrinks the inputs to find a smaller counter-example. The crate ships generators for integers, floats, tuples, booleans, lists, strings, options and results, so the common shapes of Rust data are covered without writing a generator. The audience is Rust developers who can express their expectations as an invariant over a whole input domain rather than as a table of examples. A function that reverses a slice, a codec that must round-trip, a range query that must return a subset of the original set: these are the natural fits. It is less useful for code whose correctness depends on external state, timing or IO, because the property function has to be callable with generated values alone.

## How the generator, the Testable trait and shrinking fit together

The entry point is generic over a trait, not over a fixed signature. The README quotes the signature as pub fn quickcheck<A: Testable>(f: A), and Testable is defined as fn result(&self, &mut Gen) -> TestResult. A bool is Testable, and so is a function with one parameter that implements Arbitrary and a return type that implements Testable. That is why a plain fn(usize) -> bool can be handed to quickcheck without ceremony: usize satisfies Arbitrary, bool satisfies Testable, and the function becomes testable by construction. Generation comes from Gen, the source of randomness passed into result. Once a property fails, the crate shrinks the inputs. The README states that the shrinking strategies for lists and numbers use a binary search to cover the input space quickly, and that this should be the same strategy used in Koen Claessen's QuickCheck for Haskell. Shrinking is what turns a failing 400-element vector into a two-element one you can read. The README also describes the discard mechanism: a property that only holds for a subset of inputs should neither pass nor fail outside that subset, and TestResult records whether a test passed, failed or was discarded. That is the piece most people miss on a first read, because returning false for an out-of-scope input is not the same as discarding it.

## Installing quickcheck and running a first property

The crate is on crates.io. The README shows adding it as a regular dependency, or as a development dependency when it is only used in test code. For the macro form you need quickcheck_macros as well.

```toml
[dev-dependencies]
quickcheck = "1"
quickcheck_macros = "1"
```

The README's simple example defines a reverse function and a module that imports quickcheck::quickcheck and the reverse function under test. The quickcheck! macro takes a named function whose parameters are the generated inputs and whose return type is bool.

```rust
use quickcheck::quickcheck;

quickcheck! {
    fn prop(xs: Vec<u32>) -> bool {
        xs == reverse(&reverse(&xs))
    }
}
```

The README notes that this macro is backwards compatible with old versions of Rust. If you prefer an attribute, the second form imports quickcheck_macros::quickcheck and annotates the property so it is converted into a #[test] function, which means it runs with cargo test alongside your other tests.

```rust
use quickcheck_macros::quickcheck;

#[quickcheck]
fn double_reversal_is_identity(xs: Vec<isize>) -> bool {
    xs == reverse(&reverse(&xs))
}
```

One environment detail matters when you read CI output. The README states that RUST_LOG=quickcheck enables info! logging so the run shows useful output such as the number of tests passed, and that this is not needed to show witnesses for failures. So a failing property prints its counter-example either way; only the pass counts and similar detail require the log level. The default features are regex and use_logging, and the README lists use_logging as governing the RUST_LOG messages and regex as enabling regexes with env_logger.

## The shrinking caveat the README states plainly

The compatibility section is the most important paragraph in the repository for anyone planning to depend on this crate long term. It says the crate considers the Arbitrary implementations provided as implementation details, that strategies may or may not change over time, and that this may cause new test failures, presumably because a new kind of witness is being generated. It then says these changes may happen in semver compatible releases. Read that as a design decision with a cost: a green suite on quickcheck 1.0.x is not a promise that the same suite is green on 1.y, because generation is allowed to change under a minor bump. If your workflow treats any new test failure as a regression in your own code, you will occasionally be wrong. The same section is also the reason the crate can improve its generators without a major version. The second constraint is the toolchain. The Cargo.toml sets rust-version = "1.85.0", and the README's minimum Rust version policy says the minimum can be increased in minor version updates, with the caveat that rand is a public dependency and quickcheck defers to rand's MSRV policy when that is more aggressive. So the effective floor is whichever of the two is higher, and it can move on a minor release.

## When quickcheck is the wrong tool

Three cases stand out. First, constrained input domains. If a valid input must satisfy a structural rule (sorted, unique, non-empty, within a range), a raw generator will spend its budget producing values you discard, and the discard mechanism exists precisely because that is awkward. Second, reproducibility across upgrades, as described above: the README explicitly frames Arbitrary implementations as implementation details, so a failing seed today is not guaranteed to fail tomorrow. Third, when you need to explore a parser or a binary format for panics rather than check a mathematical property. The README points elsewhere for that: it refers to the Rust Fuzz Book and the arbitrary crate as alternatives for fuzzing. That is a different activity with a different budget, and treating quickcheck as a fuzzer understates what coverage-guided fuzzing does. There is also a documentation gap worth naming. The README covers the macro, the attribute, the Testable trait and the discard mechanism, but it does not document how to pin a seed or replay a specific failing input, and the repository has no release notes describing such a feature. If deterministic replay is a requirement for your team, verify it against docs.rs before you commit.

## quickcheck against proptest

The README names proptest directly and links to two comparisons, one in proptest's own README and one in a proptest issue comment. It says proptest is inspired by the Hypothesis framework for Python and, in particular, that proptest improves on the concept of shrinking. It then adds a sentence that is unusually candid for a README: if you have ever had problems or frustration with shrinking in quickcheck, proptest might be worth a try. The practical difference in approach is where the strategy lives. In quickcheck, generation comes from the Arbitrary implementation for a type, which the compatibility section treats as an implementation detail of the crate. In proptest, strategies are values you compose in the test, so the input distribution is part of your test code and visible next to the property. That is a real trade: quickcheck asks less of you, proptest gives you more control and more to write. For a small invariant over standard types, quickcheck's cost is a dev-dependency and a few lines. For a domain model with constrained fields, proptest's explicit strategies are likely to be less work overall than fighting discards.

## Licence, workspace layout and upgrade cost

The Cargo.toml declares license = "Unlicense OR MIT", and the repository root carries LICENSE-MIT, UNLICENSE and COPYING, while the README says dual-licensed under MIT or the UNLICENSE. Either option is permissive, so the choice is about attribution practice rather than about what you are allowed to do; this is a description of the declared terms, not legal advice, and your organisation's policy decides which of the two you take. The crate is part of a Cargo workspace whose members list is ["quickcheck_macros"], so the attribute macro lives in a sibling crate in the same repository and is versioned alongside it. That matters for upgrades: adding the attribute means tracking two crates, and the README's installation section shows both pinned at "1". The dependency graph is small. rand is a required dependency at version 0.10 with default-features = false and the sys_rng feature, log and env_logger are optional and pulled in by the default features, and the README notes that rand is a public dependency. Upgrading therefore means watching three things: the rustc floor of 1.85.0, rand's own MSRV, and the possibility of new failures from changed Arbitrary strategies. The last push to the repository was on 2026-04-03.

## Conclusion

Adopt quickcheck when you already have pure functions with invariants (encode/decode pairs, sort and reverse, range queries) and you want a small dev-dependency rather than a framework. Do not adopt it if your inputs need structural constraints or if a failure must reproduce exactly across releases: the README states that Arbitrary implementations are implementation details and that strategies may change in semver compatible releases. Before committing, verify that your toolchain is at least rustc 1.85.0, check that rand 0.10 resolves in your lockfile, and confirm that RUST_LOG=quickcheck output is acceptable in your CI logs.

## FAQ

### What is quickcheck in Rust?

It is a property based testing crate: you write a property function, it generates random inputs from the Arbitrary implementations for those types, and when the property fails it shrinks the inputs to a smaller counter-example. The README describes it as a way to do property based testing using randomly generated input.

### How do I install quickcheck in a Rust project?

Add quickcheck = "1" to your dependencies, or to [dev-dependencies] if it is only used in test code. To use the #[quickcheck] attribute, add quickcheck_macros = "1" as well.

### How do I use quickcheck to test a property?

Write a function that takes generated inputs and returns bool, then wrap it in the quickcheck! macro, or annotate it with #[quickcheck] from quickcheck_macros so it becomes a #[test] function. The README's example checks that reversing a vector twice returns the original vector.

## Sources

- [BurntSushi/quickcheck on GitHub](https://github.com/BurntSushi/quickcheck)
- [Issues](https://github.com/BurntSushi/quickcheck/issues)
- [License: Unlicense](https://github.com/BurntSushi/quickcheck/blob/master/LICENSE)
- [README](https://github.com/BurntSushi/quickcheck/blob/master/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/burntsushi-quickcheck
