Import googletest 0.10.0 crate

Tests are not enabled since they depend on crates which we don't have in
AOSP. I’ll try to improve this upstream.

Bug: 293589104
Bug: 292458387
Test: m libgoogletest
Change-Id: If4b9409cb498288c590beb2a343eb378f66cf813
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
new file mode 100644
index 0000000..ce5ec95
--- /dev/null
+++ b/.cargo_vcs_info.json
@@ -0,0 +1,6 @@
+{
+  "git": {
+    "sha1": "6e5405db2217d37006720c101beb1b91199a3a26"
+  },
+  "path_in_vcs": "googletest"
+}
\ No newline at end of file
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..9f2e45e
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,28 @@
+// This file is generated by cargo2android.py --config cargo2android.json.
+// Do not modify this file as changes will be overridden on upgrade.
+
+
+
+rust_library {
+    name: "libgoogletest_rust",
+    host_supported: true,
+    crate_name: "googletest",
+    cargo_env_compat: true,
+    cargo_pkg_version: "0.10.0",
+    srcs: ["src/lib.rs"],
+    edition: "2021",
+    rustlibs: [
+        "libnum_traits",
+        "libregex",
+    ],
+    proc_macros: [
+        "libgoogletest_macro",
+        "librustversion",
+    ],
+    apex_available: [
+        "//apex_available:platform",
+        "//apex_available:anyapex",
+    ],
+    product_available: true,
+    vendor_available: true,
+}
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..3e898d2
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,66 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2021"
+rust-version = "1.59.0"
+name = "googletest"
+version = "0.10.0"
+authors = [
+    "Bradford Hovinen <[email protected]>",
+    "Bastien Jacot-Guillarmod <[email protected]>",
+    "Maciej Pietrzak <[email protected]>",
+    "Martin Geisler <[email protected]>",
+]
+description = "A rich assertion and matcher library inspired by GoogleTest for C++"
+readme = "README.md"
+keywords = [
+    "unit",
+    "matcher",
+    "testing",
+    "assertions",
+]
+categories = [
+    "development-tools",
+    "development-tools::testing",
+]
+license = "Apache-2.0"
+repository = "https://github.com/google/googletest-rust"
+resolver = "1"
+
+[dependencies.anyhow]
+version = "1"
+optional = true
+
+[dependencies.googletest_macro]
+version = "0.10.0"
+
+[dependencies.num-traits]
+version = "0.2.15"
+
+[dependencies.proptest]
+version = "1.2.0"
+optional = true
+
+[dependencies.regex]
+version = "1.6.0"
+
+[dependencies.rustversion]
+version = "1.0.14"
+
+[dev-dependencies.indoc]
+version = "2"
+
+[dev-dependencies.quickcheck]
+version = "1.0.3"
+
+[dev-dependencies.serial_test]
+version = "2.0.0"
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..6c2060b
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,19 @@
+name: "googletest"
+description: "A rich assertion and matcher library inspired by GoogleTest for C++"
+third_party {
+  identifier {
+    type: "crates.io"
+    value: "https://crates.io/crates/googletest"
+  }
+  identifier {
+    type: "Archive"
+    value: "https://static.crates.io/crates/googletest/googletest-0.10.0.crate"
+  }
+  version: "0.10.0"
+  license_type: NOTICE
+  last_upgrade_date {
+    year: 2023
+    month: 9
+    day: 1
+  }
+}
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_APACHE2
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..45dc4dd
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1 @@
+include platform/prebuilts/rust:master:/OWNERS
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..2a0f918
--- /dev/null
+++ b/README.md
@@ -0,0 +1,354 @@
+# GoogleTest Rust
+
+[![crates.io][crates-badge]][crates-url]
+[![docs.rs][docs-badge]][docs-url]
+[![Apache licensed][license-badge]][license-url]
+[![Build Status][actions-badge]][actions-url]
+
+[crates-badge]: https://img.shields.io/crates/v/googletest.svg
+[crates-url]: https://crates.io/crates/googletest
+[docs-badge]: https://img.shields.io/badge/docs.rs-googletest-66c2a5
+[docs-url]: https://docs.rs/googletest/*/googletest/
+[license-badge]: https://img.shields.io/badge/license-Apache-blue.svg
+[license-url]: https://github.com/google/googletest-rust/blob/main/LICENSE
+[actions-badge]: https://github.com/google/googletest-rust/workflows/CI/badge.svg
+[actions-url]: https://github.com/google/googletest-rust/actions?query=workflow%3ACI+branch%3Amain
+
+This library brings the rich assertion types of Google's C++ testing library
+[GoogleTest](https://github.com/google/googletest) to Rust. It provides:
+
+ * A framework for writing matchers which can be combined to make a wide range
+   of assertions on data,
+ * A rich set of matchers providing similar functionality to those included in
+   [GoogleTest](https://google.github.io/googletest/reference/matchers.html),
+   and
+ * A new set of assertion macros offering similar functionality to those of
+   [GoogleTest](https://google.github.io/googletest/primer.html#assertions).
+
+**The minimum supported Rust version is 1.59**. We recommend using at least
+version 1.66 for the best developer experience.
+
+> :warning: The API is not fully stable and may still be changed until we
+> publish version 1.0.
+
+## Assertions and matchers
+
+The core of GoogleTest is its *matchers*. Matchers indicate what aspect of an
+actual value one is asserting: (in-)equality, containment, regular expression
+matching, and so on.
+
+To make an assertion using a matcher, GoogleTest offers three macros:
+
+ * [`assert_that!`] panics if the assertion fails, aborting the test.
+ * [`expect_that!`] logs an assertion failure, marking the test as having
+   failed, but allows the test to continue running (called a _non-fatal
+   assertion_). It requires the use of the [`googletest::test`] attribute macro
+   on the test itself.
+ * [`verify_that!`] has no side effects and evaluates to a [`Result<()>`] whose
+   `Err` variant describes the assertion failure, if there is one. In
+   combination with the
+   [`?` operator](https://doc.rust-lang.org/reference/expressions/operator-expr.html#the-question-mark-operator),
+   this can be used to abort the test on assertion failure without panicking. It
+   is also the building block for the other two macros above.
+
+For example:
+
+```rust
+use googletest::prelude::*;
+
+#[test]
+fn fails_and_panics() {
+    let value = 2;
+    assert_that!(value, eq(4));
+}
+
+#[googletest::test]
+fn two_logged_failures() {
+    let value = 2;
+    expect_that!(value, eq(4)); // Test now failed, but continues executing.
+    expect_that!(value, eq(5)); // Second failure is also logged.
+}
+
+#[test]
+fn fails_immediately_without_panic() -> Result<()> {
+    let value = 2;
+    verify_that!(value, eq(4))?; // Test fails and aborts.
+    verify_that!(value, eq(2))?; // Never executes.
+    Ok(())
+}
+
+#[test]
+fn simple_assertion() -> Result<()> {
+    let value = 2;
+    verify_that!(value, eq(4)) // One can also just return the last assertion.
+}
+```
+
+This library includes a rich set of matchers, covering:
+
+ * Equality, numeric inequality, and approximate equality;
+ * Strings and regular expressions;
+ * Containers and set-theoretic matching.
+
+Matchers are composable:
+
+```rust
+use googletest::prelude::*;
+
+#[googletest::test]
+fn contains_at_least_one_item_at_least_3() {
+    let value = vec![1, 2, 3];
+    expect_that!(value, contains(ge(3)));
+}
+```
+
+They can also be logically combined:
+
+```rust
+use googletest::prelude::*;
+
+#[googletest::test]
+fn strictly_between_9_and_11() {
+    let value = 10;
+    expect_that!(value, gt(9).and(not(ge(11))));
+}
+```
+
+## Pattern-matching
+
+One can use the macro [`matches_pattern!`] to create a composite matcher for a
+struct or enum that matches fields with other matchers:
+
+```rust
+use googletest::prelude::*;
+
+struct AStruct {
+    a_field: i32,
+    another_field: i32,
+    a_third_field: &'static str,
+}
+
+#[test]
+fn struct_has_expected_values() {
+    let value = AStruct {
+        a_field: 10,
+        another_field: 100,
+        a_third_field: "A correct value",
+    };
+    expect_that!(value, matches_pattern!(AStruct {
+        a_field: eq(10),
+        another_field: gt(50),
+        a_third_field: contains_substring("correct"),
+    }));
+}
+```
+
+## Writing matchers
+
+One can extend the library by writing additional matchers. To do so, create a
+struct holding the matcher's data and have it implement the trait [`Matcher`]:
+
+```rust
+struct MyEqMatcher<T> {
+    expected: T,
+}
+
+impl<T: PartialEq + Debug> Matcher for MyEqMatcher<T> {
+    type ActualT = T;
+
+    fn matches(&self, actual: &Self::ActualT) -> MatcherResult {
+         (self.expected == *actual).into()
+    }
+
+    fn describe(&self, matcher_result: MatcherResult) -> String {
+        match matcher_result {
+            MatcherResult::Matches => {
+                format!("is equal to {:?} the way I define it", self.expected)
+            }
+            MatcherResult::DoesNotMatch => {
+                format!("isn't equal to {:?} the way I define it", self.expected)
+            }
+        }
+    }
+}
+```
+
+It is recommended to expose a function which constructs the matcher:
+
+```rust
+pub fn eq_my_way<T: PartialEq + Debug>(expected: T) -> impl Matcher<ActualT = T> {
+    MyEqMatcher { expected }
+}
+```
+
+The new matcher can then be used in the assertion macros:
+
+```rust
+#[googletest::test]
+fn should_be_equal_by_my_definition() {
+    expect_that!(10, eq_my_way(10));
+}
+```
+
+## Non-fatal assertions
+
+Using non-fatal assertions, a single test is able to log multiple assertion
+failures. Any single assertion failure causes the test to be considered having
+failed, but execution continues until the test completes or otherwise aborts.
+
+This is analogous to the `EXPECT_*` family of macros in GoogleTest.
+
+To make a non-fatal assertion, use the macro [`expect_that!`]. The test must
+also be marked with [`googletest::test`] instead of the Rust-standard `#[test]`.
+
+```rust
+use googletest::prelude::*;
+
+#[googletest::test]
+fn three_non_fatal_assertions() {
+    let value = 2;
+    expect_that!(value, eq(2));  // Passes; test still considered passing.
+    expect_that!(value, eq(3));  // Fails; logs failure and marks the test failed.
+    expect_that!(value, eq(4));  // A second failure, also logged.
+}
+```
+
+This can be used in the same tests as `verify_that!`, in which case the test
+function must also return [`Result<()>`]:
+
+```rust
+use googletest::prelude::*;
+
+#[googletest::test]
+fn failing_non_fatal_assertion() -> Result<()> {
+    let value = 2;
+    expect_that!(value, eq(3));  // Just marks the test as having failed.
+    verify_that!(value, eq(2))?;  // Passes, so does not abort the test.
+    Ok(())        // Because of the failing expect_that! call above, the
+                  // test fails despite returning Ok(())
+}
+```
+
+```rust
+use googletest::prelude::*;
+
+#[googletest::test]
+fn failing_fatal_assertion_after_non_fatal_assertion() -> Result<()> {
+    let value = 2;
+    verify_that!(value, eq(3))?; // Fails and aborts the test.
+    expect_that!(value, eq(3));  // Never executes, since the test already aborted.
+    Ok(())
+}
+```
+
+### Interoperability
+
+You can use the `#[googletest::test]` macro together with many other libraries
+such as [rstest](https://crates.io/crates/rstest). Just apply both attribute
+macros to the test:
+
+```rust
+#[googletest::test]
+#[rstest]
+#[case(1)]
+#[case(2)]
+#[case(3)]
+fn rstest_works_with_google_test(#[case] value: u32) -> Result<()> {
+   verify_that!(value, gt(0))
+}
+```
+
+Make sure to put `#[googletest::test]` *before* `#[rstest]`. Otherwise the
+annotated test will run twice, since both macros will attempt to register a test
+with the Rust test harness.
+
+The macro also works together with
+[async tests with Tokio](https://docs.rs/tokio/latest/tokio/attr.test.html) in
+the same way:
+
+```rust
+#[googletest::test]
+#[tokio::test]
+async fn should_work_with_tokio() -> Result<()> {
+    verify_that!(3, gt(0))
+}
+```
+
+There is one caveat when running async tests: test failure reporting through
+`and_log_failure` will not work properly if the assertion occurs on a different
+thread than runs the test.
+
+## Predicate assertions
+
+The macro [`verify_pred!`] provides predicate assertions analogous to
+GoogleTest's `EXPECT_PRED` family of macros. Wrap an invocation of a predicate
+in a `verify_pred!` invocation to turn that into a test assertion which passes
+precisely when the predicate returns `true`:
+
+```rust
+fn stuff_is_correct(x: i32, y: i32) -> bool {
+    x == y
+}
+
+let x = 3;
+let y = 4;
+verify_pred!(stuff_is_correct(x, y))?;
+```
+
+The assertion failure message shows the arguments and the values to which they
+evaluate:
+
+```
+stuff_is_correct(x, y) was false with
+  x = 3,
+  y = 4
+```
+
+The `verify_pred!` invocation evaluates to a [`Result<()>`] just like
+[`verify_that!`]. There is also a macro [`expect_pred!`] to make a non-fatal
+predicaticate assertion.
+
+## Unconditionally generating a test failure
+
+The macro [`fail!`] unconditionally evaluates to a `Result` indicating a test
+failure. It can be used analogously to [`verify_that!`] and [`verify_pred!`] to
+cause a test to fail, with an optional formatted message:
+
+```rust
+#[test]
+fn always_fails() -> Result<()> {
+    fail!("This test must fail with {}", "today")
+}
+```
+
+## Configuration
+
+This library is configurable through environment variables. Since the
+configuration does not impact whether a test fails or not but how a failure is
+displayed, we recommend setting those variables in the personal
+`~/.cargo/config.toml` instead of in the project-scoped `Cargo.toml`.
+
+### Configuration variable list
+
+| Variable name | Description                                             |
+| ------------- | ------------------------------------------------------- |
+| NO_COLOR      | Disables colored output. See <https://no-color.org/>.   |
+| FORCE_COLOR   | Forces colors even when the output is piped to a file.  |
+
+## Contributing Changes
+
+Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute
+to this project.
+
+[`and_log_failure()`]: https://docs.rs/googletest/*/googletest/trait.GoogleTestSupport.html#tymethod.and_log_failure
+[`assert_that!`]: https://docs.rs/googletest/*/googletest/macro.assert_that.html
+[`expect_pred!`]: https://docs.rs/googletest/*/googletest/macro.expect_pred.html
+[`expect_that!`]: https://docs.rs/googletest/*/googletest/macro.expect_that.html
+[`fail!`]: https://docs.rs/googletest/*/googletest/macro.fail.html
+[`googletest::test`]: https://docs.rs/googletest/*/googletest/attr.test.html
+[`matches_pattern!`]: https://docs.rs/googletest/*/googletest/macro.matches_pattern.html
+[`verify_pred!`]: https://docs.rs/googletest/*/googletest/macro.verify_pred.html
+[`verify_that!`]: https://docs.rs/googletest/*/googletest/macro.verify_that.html
+[`Describe`]: https://docs.rs/googletest/*/googletest/matcher/trait.Describe.html
+[`Matcher`]: https://docs.rs/googletest/*/googletest/matcher/trait.Matcher.html
+[`Result<()>`]: https://docs.rs/googletest/*/googletest/type.Result.html
diff --git a/cargo2android.json b/cargo2android.json
new file mode 100644
index 0000000..c76e391
--- /dev/null
+++ b/cargo2android.json
@@ -0,0 +1,5 @@
+{
+  "device": true,
+  "run": true,
+  "suffix": "_rust"
+}
\ No newline at end of file
diff --git a/config.toml b/config.toml
new file mode 100644
index 0000000..7bc2412
--- /dev/null
+++ b/config.toml
@@ -0,0 +1,2 @@
+[env]
+NO_COLOR = "1"
diff --git a/crate_docs.md b/crate_docs.md
new file mode 100644
index 0000000..a00c219
--- /dev/null
+++ b/crate_docs.md
@@ -0,0 +1,485 @@
+A rich test assertion library for Rust.
+
+This library provides:
+
+ * A framework for writing matchers which can be combined to make a wide
+   range of assertions on data,
+ * A rich set of matchers, and
+ * A new set of test assertion macros.
+
+## Assertions and matchers
+
+The core of GoogleTest is its *matchers*. Matchers indicate what aspect of an
+actual value one is asserting: (in-)equality, containment, regular expression
+matching, and so on.
+
+To make an assertion using a matcher, GoogleTest offers three macros:
+
+ * [`assert_that!`] panics if the assertion fails, aborting the test.
+ * [`expect_that!`] logs an assertion failure, marking the test as having
+   failed, but allows the test to continue running (called a _non-fatal
+   assertion_). It requires the use of the [`googletest::test`][crate::test]
+   attribute macro on the test itself.
+ * [`verify_that!`] has no side effects and evaluates to a [`Result`] whose
+   `Err` variant describes the assertion failure, if there is one. In
+   combination with the
+   [`?` operator](https://doc.rust-lang.org/reference/expressions/operator-expr.html#the-question-mark-operator),
+   this can be used to abort the test on assertion failure without panicking. It
+   is also the building block for the other two macros above.
+
+For example:
+
+```
+use googletest::prelude::*;
+
+# /* The attribute macro would prevent the function from being compiled in a doctest.
+#[test]
+# */
+fn fails_and_panics() {
+    let value = 2;
+    assert_that!(value, eq(4));
+}
+
+# /* The attribute macro would prevent the function from being compiled in a doctest.
+#[googletest::test]
+# */
+fn two_logged_failures() {
+    let value = 2;
+    expect_that!(value, eq(4)); // Test now failed, but continues executing.
+    expect_that!(value, eq(5)); // Second failure is also logged.
+}
+
+# /* The attribute macro would prevent the function from being compiled in a doctest.
+#[test]
+# */
+fn fails_immediately_without_panic() -> Result<()> {
+    let value = 2;
+    verify_that!(value, eq(4))?; // Test fails and aborts.
+    verify_that!(value, eq(2))?; // Never executes.
+    Ok(())
+}
+
+# /* The attribute macro would prevent the function from being compiled in a doctest.
+#[test]
+# */
+fn simple_assertion() -> Result<()> {
+    let value = 2;
+    verify_that!(value, eq(4)) // One can also just return the last assertion.
+}
+```
+
+Matchers are composable:
+
+```
+use googletest::prelude::*;
+
+# /* The attribute macro would prevent the function from being compiled in a doctest.
+#[googletest::test]
+# */
+fn contains_at_least_one_item_at_least_3() {
+# googletest::internal::test_outcome::TestOutcome::init_current_test_outcome();
+    let value = vec![1, 2, 3];
+    expect_that!(value, contains(ge(3)));
+# googletest::internal::test_outcome::TestOutcome::close_current_test_outcome::<&str>(Ok(()))
+#     .unwrap();
+}
+# contains_at_least_one_item_at_least_3();
+```
+
+They can also be logically combined:
+
+```
+use googletest::prelude::*;
+
+# /* The attribute macro would prevent the function from being compiled in a doctest.
+#[googletest::test]
+# */
+fn strictly_between_9_and_11() {
+# googletest::internal::test_outcome::TestOutcome::init_current_test_outcome();
+    let value = 10;
+    expect_that!(value, gt(9).and(not(ge(11))));
+# googletest::internal::test_outcome::TestOutcome::close_current_test_outcome::<&str>(Ok(()))
+#     .unwrap();
+}
+# strictly_between_9_and_11();
+```
+
+## Available matchers
+
+The following matchers are provided in GoogleTest Rust:
+
+| Matcher              | What it matches                                                          |
+|----------------------|--------------------------------------------------------------------------|
+| [`all!`]             | Anything matched by all given matchers.                                  |
+| [`any!`]             | Anything matched by at least one of the given matchers.                  |
+| [`anything`]         | Any input.                                                               |
+| [`approx_eq`]        | A floating point number within a standard tolerance of the argument.     |
+| [`char_count`]       | A string with a Unicode scalar count matching the argument.              |
+| [`container_eq`]     | Same as [`eq`], but for containers (with a better mismatch description). |
+| [`contains`]         | A container containing an element matched by the given matcher.          |
+| [`contains_each!`]   | A container containing distinct elements each of the arguments match.    |
+| [`contains_regex`]   | A string containing a substring matching the given regular expression.   |
+| [`contains_substring`] | A string containing the given substring.                               |
+| [`displays_as`]      | A [`Display`] value whose formatted string is matched by the argument.   |
+| [`each`]             | A container all of whose elements the given argument matches.            |
+| [`elements_are!`]    | A container whose elements the arguments match, in order.                |
+| [`empty`]            | An empty collection.                                                     |
+| [`ends_with`]        | A string ending with the given suffix.                                   |
+| [`eq`]               | A value equal to the argument, in the sense of the [`PartialEq`] trait.  |
+| [`eq_deref_of`]      | A value equal to the dereferenced value of the argument.                 |
+| [`err`]              | A [`Result`][std::result::Result] containing an `Err` variant the argument matches. |
+| [`field!`]           | A struct or enum with a given field whose value the argument matches.    |
+| [`ge`]               | A [`PartialOrd`] value greater than or equal to the given value.         |
+| [`gt`]               | A [`PartialOrd`] value strictly greater than the given value.            |
+| [`has_entry`]        | A [`HashMap`] containing a given key whose value the argument matches.   |
+| [`is_contained_in!`] | A container each of whose elements is matched by some given matcher.     |
+| [`is_nan`]           | A floating point number which is NaN.                                    |
+| [`le`]               | A [`PartialOrd`] value less than or equal to the given value.            |
+| [`len`]              | A container whose number of elements the argument matches.               |
+| [`lt`]               | A [`PartialOrd`] value strictly less than the given value.               |
+| [`matches_pattern!`] | A struct or enum whose fields are matched according to the arguments.    |
+| [`matches_regex`]    | A string matched by the given regular expression.                        |
+| [`near`]             | A floating point number within a given tolerance of the argument.        |
+| [`none`]             | An [`Option`] containing `None`.                                         |
+| [`not`]              | Any value the argument does not match.                                   |
+| [`ok`]               | A [`Result`][std::result::Result] containing an `Ok` variant the argument matches. |
+| [`pat!`]             | Alias for [`matches_pattern!`].                                          |
+| [`points_to`]        | Any [`Deref`] such as `&`, `Rc`, etc. whose value the argument matches.  |
+| [`pointwise!`]       | A container whose contents the arguments match in a pointwise fashion.   |
+| [`predicate`]        | A value on which the given predicate returns true.                       |
+| [`some`]             | An [`Option`] containing `Some` whose value the argument matches.        |
+| [`starts_with`]      | A string starting with the given prefix.                                 |
+| [`subset_of`]        | A container all of whose elements are contained in the argument.         |
+| [`superset_of`]      | A container containing all elements of the argument.                     |
+| [`unordered_elements_are!`] | A container whose elements the arguments match, in any order.     |
+
+[`anything`]: matchers::anything
+[`approx_eq`]: matchers::approx_eq
+[`char_count`]: matchers::char_count
+[`container_eq`]: matchers::container_eq
+[`contains`]: matchers::contains
+[`contains_regex`]: matchers::contains_regex
+[`contains_substring`]: matchers::contains_substring
+[`displays_as`]: matchers::displays_as
+[`each`]: matchers::each
+[`empty`]: matchers::empty
+[`ends_with`]: matchers::ends_with
+[`eq`]: matchers::eq
+[`eq_deref_of`]: matchers::eq_deref_of
+[`err`]: matchers::err
+[`ge`]: matchers::ge
+[`gt`]: matchers::gt
+[`has_entry`]: matchers::has_entry
+[`is_nan`]: matchers::is_nan
+[`le`]: matchers::le
+[`len`]: matchers::len
+[`lt`]: matchers::lt
+[`matches_regex`]: matchers::matches_regex
+[`near`]: matchers::near
+[`none`]: matchers::none
+[`not`]: matchers::not
+[`ok`]: matchers::ok
+[`points_to`]: matchers::points_to
+[`predicate`]: matchers::predicate
+[`some`]: matchers::some
+[`starts_with`]: matchers::starts_with
+[`subset_of`]: matchers::subset_of
+[`superset_of`]: matchers::superset_of
+[`Deref`]: std::ops::Deref
+[`Display`]: std::fmt::Display
+[`HashMap`]: std::collections::HashMap
+[`Option`]: std::option::Option
+[`PartialEq`]: std::cmp::PartialEq
+[`PartialOrd`]: std::cmp::PartialOrd
+
+## Writing matchers
+
+One can extend the library by writing additional matchers. To do so, create
+a struct holding the matcher's data and have it implement the trait
+[`Matcher`]:
+
+```no_run
+use googletest::matcher::{Matcher, MatcherResult};
+use std::fmt::Debug;
+
+struct MyEqMatcher<T> {
+    expected: T,
+}
+
+impl<T: PartialEq + Debug> Matcher for MyEqMatcher<T> {
+    type ActualT = T;
+
+    fn matches(&self, actual: &Self::ActualT) -> MatcherResult {
+        if self.expected == *actual {
+            MatcherResult::Match
+        } else {
+            MatcherResult::NoMatch
+        }
+    }
+
+    fn describe(&self, matcher_result: MatcherResult) -> String {
+        match matcher_result {
+            MatcherResult::Match => {
+                format!("is equal to {:?} the way I define it", self.expected)
+            }
+            MatcherResult::NoMatch => {
+                format!("isn't equal to {:?} the way I define it", self.expected)
+            }
+        }
+    }
+}
+```
+
+ It is recommended to expose a function which constructs the matcher:
+
+ ```no_run
+ # use googletest::matcher::{Matcher, MatcherResult};
+ # use std::fmt::Debug;
+ #
+ # struct MyEqMatcher<T> {
+ #    expected: T,
+ # }
+ #
+ # impl<T: PartialEq + Debug> Matcher for MyEqMatcher<T> {
+ #    type ActualT = T;
+ #
+ #    fn matches(&self, actual: &Self::ActualT) -> MatcherResult {
+ #        if self.expected == *actual {
+ #            MatcherResult::Match
+ #        } else {
+ #            MatcherResult::NoMatch
+ #        }
+ #    }
+ #
+ #    fn describe(&self, matcher_result: MatcherResult) -> String {
+ #        match matcher_result {
+ #            MatcherResult::Match => {
+ #                format!("is equal to {:?} the way I define it", self.expected)
+ #            }
+ #            MatcherResult::NoMatch => {
+ #                format!("isn't equal to {:?} the way I define it", self.expected)
+ #            }
+ #        }
+ #    }
+ # }
+ #
+ pub fn eq_my_way<T: PartialEq + Debug>(expected: T) -> impl Matcher<ActualT = T> {
+    MyEqMatcher { expected }
+ }
+ ```
+
+ The new matcher can then be used in the assertion macros:
+
+```
+# use googletest::prelude::*;
+# use googletest::matcher::{Matcher, MatcherResult};
+# use std::fmt::Debug;
+#
+# struct MyEqMatcher<T> {
+#    expected: T,
+# }
+#
+# impl<T: PartialEq + Debug> Matcher for MyEqMatcher<T> {
+#    type ActualT = T;
+#
+#    fn matches(&self, actual: &Self::ActualT) -> MatcherResult {
+#        if self.expected == *actual {
+#            MatcherResult::Match
+#        } else {
+#            MatcherResult::NoMatch
+#        }
+#    }
+#
+#    fn describe(&self, matcher_result: MatcherResult) -> String {
+#        match matcher_result {
+#            MatcherResult::Match => {
+#                format!("is equal to {:?} the way I define it", self.expected)
+#            }
+#            MatcherResult::NoMatch => {
+#                format!("isn't equal to {:?} the way I define it", self.expected)
+#            }
+#        }
+#    }
+# }
+#
+# pub fn eq_my_way<T: PartialEq + Debug>(expected: T) -> impl Matcher<ActualT = T> {
+#    MyEqMatcher { expected }
+# }
+# /* The attribute macro would prevent the function from being compiled in a doctest.
+#[googletest::test]
+# */
+fn should_be_equal_by_my_definition() {
+# googletest::internal::test_outcome::TestOutcome::init_current_test_outcome();
+    expect_that!(10, eq_my_way(10));
+# googletest::internal::test_outcome::TestOutcome::close_current_test_outcome::<&str>(Ok(()))
+#     .unwrap();
+}
+# should_be_equal_by_my_definition();
+```
+
+## Non-fatal assertions
+
+Using non-fatal assertions, a single test is able to log multiple assertion
+failures. Any single assertion failure causes the test to be considered
+having failed, but execution continues until the test completes or otherwise
+aborts.
+
+To make a non-fatal assertion, use the macro [`expect_that!`]. The test must
+also be marked with [`googletest::test`][crate::test] instead of the
+Rust-standard `#[test]`.
+
+```no_run
+use googletest::prelude::*;
+
+#[googletest::test]
+fn three_non_fatal_assertions() {
+    let value = 2;
+    expect_that!(value, eq(2));  // Passes; test still considered passing.
+    expect_that!(value, eq(3));  // Fails; logs failure and marks the test failed.
+    expect_that!(value, eq(4));  // A second failure, also logged.
+}
+```
+
+This can be used in the same tests as `verify_that!`, in which case the test
+function must also return [`Result<()>`]:
+
+```no_run
+use googletest::prelude::*;
+
+# /* Make sure this also compiles as a doctest.
+#[googletest::test]
+# */
+fn failing_non_fatal_assertion() -> Result<()> {
+    let value = 2;
+    expect_that!(value, eq(3));  // Just marks the test as having failed.
+    verify_that!(value, eq(2))?;  // Passes, so does not abort the test.
+    Ok(())        // Because of the failing expect_that! call above, the
+                  // test fails despite returning Ok(())
+}
+```
+
+```no_run
+use googletest::prelude::*;
+
+#[googletest::test]
+fn failing_fatal_assertion_after_non_fatal_assertion() -> Result<()> {
+    let value = 2;
+    expect_that!(value, eq(2));  // Passes; test still considered passing.
+    verify_that!(value, eq(3))?; // Fails and aborts the test.
+    expect_that!(value, eq(3));  // Never executes, since the test already aborted.
+    Ok(())
+}
+```
+
+## Predicate assertions
+
+The macro [`verify_pred!`] provides predicate assertions analogous to
+GoogleTest's `EXPECT_PRED` family of macros. Wrap an invocation of a
+predicate in a `verify_pred!` invocation to turn that into a test assertion
+which passes precisely when the predicate returns `true`:
+
+```
+# use googletest::prelude::*;
+fn stuff_is_correct(x: i32, y: i32) -> bool {
+    x == y
+}
+
+# fn run_test() -> Result<()> {
+let x = 3;
+let y = 4;
+verify_pred!(stuff_is_correct(x, y))?;
+# Ok(())
+# }
+# run_test().unwrap_err();
+```
+
+The assertion failure message shows the arguments and the values to which
+they evaluate:
+
+```text
+stuff_is_correct(x, y) was false with
+  x = 3,
+  y = 4
+```
+
+The `verify_pred!` invocation evaluates to a [`Result<()>`] just like
+[`verify_that!`]. There is also a macro [`expect_pred!`] to make a non-fatal
+predicaticate assertion.
+
+## Unconditionally generating a test failure
+
+The macro [`fail!`] unconditionally evaluates to a `Result` indicating a
+test failure. It can be used analogously to [`verify_that!`] and
+[`verify_pred!`] to cause a test to fail, with an optional formatted
+message:
+
+```
+# use googletest::prelude::*;
+# /* The attribute macro would prevent the function from being compiled in a doctest.
+#[test]
+# */
+fn always_fails() -> Result<()> {
+    fail!("This test must fail with {}", "today")
+}
+# always_fails().unwrap_err();
+```
+
+## Integrations with other crates
+
+GoogleTest Rust includes integrations with the
+[Anyhow](https://crates.io/crates/anyhow) and
+[Proptest](https://crates.io/crates/proptest) crates to simplify turning
+errors from those crates into test failures.
+
+To use this, activate the `anyhow`, respectively `proptest` feature in
+GoogleTest Rust and invoke the extension method [`into_test_result()`] on a
+`Result` value in your test. For example:
+
+```
+# use googletest::prelude::*;
+# #[cfg(feature = "anyhow")]
+# use anyhow::anyhow;
+# #[cfg(feature = "anyhow")]
+# /* The attribute macro would prevent the function from being compiled in a doctest.
+#[test]
+# */
+fn has_anyhow_failure() -> Result<()> {
+    Ok(just_return_error().into_test_result()?)
+}
+
+# #[cfg(feature = "anyhow")]
+fn just_return_error() -> anyhow::Result<()> {
+    anyhow::Result::Err(anyhow!("This is an error"))
+}
+# #[cfg(feature = "anyhow")]
+# has_anyhow_failure().unwrap_err();
+```
+
+One can convert Proptest test failures into GoogleTest test failures when the
+test is invoked with
+[`TestRunner::run`](https://docs.rs/proptest/latest/proptest/test_runner/struct.TestRunner.html#method.run):
+
+```
+# use googletest::prelude::*;
+# #[cfg(feature = "proptest")]
+# use proptest::test_runner::{Config, TestRunner};
+# #[cfg(feature = "proptest")]
+# /* The attribute macro would prevent the function from being compiled in a doctest.
+#[test]
+# */
+fn numbers_are_greater_than_zero() -> Result<()> {
+    let mut runner = TestRunner::new(Config::default());
+    runner.run(&(1..100i32), |v| Ok(verify_that!(v, gt(0))?)).into_test_result()
+}
+# #[cfg(feature = "proptest")]
+# numbers_are_greater_than_zero().unwrap();
+```
+
+Similarly, when the `proptest` feature is enabled, GoogleTest assertion failures
+can automatically be converted into Proptest
+[`TestCaseError`](https://docs.rs/proptest/latest/proptest/test_runner/enum.TestCaseError.html)
+through the `?` operator as the example above shows.
+
+[`and_log_failure()`]: GoogleTestSupport::and_log_failure
+[`into_test_result()`]: IntoTestResult::into_test_result
+[`Matcher`]: matcher::Matcher
diff --git a/src/assertions.rs b/src/assertions.rs
new file mode 100644
index 0000000..7664486
--- /dev/null
+++ b/src/assertions.rs
@@ -0,0 +1,455 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// There are no visible documentation elements in this module; the declarative
+// macros are documented at the top level.
+#![doc(hidden)]
+
+/// Checks whether the `Matcher` given by the second argument matches the first
+/// argument.
+///
+/// Evaluates to `Result::Ok(())` if the matcher matches and
+/// `Result::Err(TestAssertionFailure)` if it does not. The caller must then
+/// decide how to handle the `Err` variant. It has a few options:
+///
+///  * Abort the current function with the `?` operator. This requires that the
+///    function return a suitable `Result`.
+///  * Log the test failure and continue by calling the method
+///    `and_log_failure`.
+///
+/// Of course, one can also use all other standard methods on `Result`.
+///
+/// **Invoking this macro by itself does not cause a test failure to be recorded
+/// or output.** The resulting `Result` must be handled as described above to
+/// cause the test to be recorded as a failure.
+///
+/// Example:
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(42, eq(42))?; // This will pass.
+/// # Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # fn should_fail() -> Result<()> {
+/// # googletest::internal::test_outcome::TestOutcome::init_current_test_outcome();
+/// verify_that!(42, eq(123)).and_log_failure();
+///             // This will log a test failure and allow execution to continue.
+/// let _ = verify_that!(42, eq(123)); // This will do nothing.
+/// verify_that!(42, eq(123))?; // This will fail, returning immediately.
+/// verify_that!(42, eq(0))?; // This will not run.
+/// # googletest::internal::test_outcome::TestOutcome::close_current_test_outcome::<&str>(Ok(()))
+/// #     .unwrap_err();
+/// # Ok(())
+/// # }
+/// # verify_that!(should_fail(), err(displays_as(contains_substring("Expected: is equal to 123"))))
+/// #     .unwrap();
+/// ```
+///
+/// This macro has special support for matching against container. Namely:
+///   * `verify_that!(actual, [m1, m2, ...])` is equivalent to
+///     `verify_that!(actual, elements_are![m1, m2, ...])`
+///   * `verify_that!(actual, {m1, m2, ...})` is equivalent to
+///     `verify_that!(actual, unordered_elements_are![m1, m2, ...])`
+///
+/// ## Matching against tuples
+///
+/// One can match against a tuple by constructing a tuple of matchers as
+/// follows:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!((123, 456), (eq(123), eq(456)))?; // Passes
+/// #     Ok(())
+/// # }
+/// # fn should_fail() -> Result<()> {
+/// verify_that!((123, 456), (eq(123), eq(0)))?; // Fails: second matcher does not match
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # should_fail().unwrap_err();
+/// ```
+///
+/// This also works with composed matchers:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!((123, 456), not((eq(456), eq(123))))?; // Passes
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// Matchers must correspond to the actual tuple in count and type. Otherwise
+/// the test will fail to compile.
+///
+/// ```compile_fail
+/// # use googletest::prelude::*;
+/// # fn should_not_compile() -> Result<()> {
+/// verify_that!((123, 456), (eq(123),))?; // Does not compile: wrong tuple size
+/// verify_that!((123, "A string"), (eq(123), eq(456)))?; // Does not compile: wrong type
+/// #     Ok(())
+/// # }
+/// ```
+///
+/// All fields must be covered by matchers. Use
+/// [`anything`][crate::matchers::anything] for fields which are not relevant
+/// for the test.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// verify_that!((123, 456), (eq(123), anything()))
+/// #     .unwrap();
+/// ```
+///
+/// This supports tuples of up to 12 elements. Tuples longer than that do not
+/// automatically inherit the `Debug` trait from their members, so are generally
+/// not supported; see [Rust by Example](https://doc.rust-lang.org/rust-by-example/primitives/tuples.html#tuples).
+#[macro_export]
+macro_rules! verify_that {
+    ($actual:expr, [$($expecteds:expr),+]) => {
+        $crate::assertions::internal::check_matcher(
+            &$actual,
+            $crate::elements_are![$($expecteds),+],
+            stringify!($actual),
+            $crate::internal::source_location::SourceLocation::new(file!(), line!(), column!()),
+        )
+    };
+    ($actual:expr, {$($expecteds:expr),+}) => {
+        $crate::assertions::internal::check_matcher(
+            &$actual,
+            $crate::unordered_elements_are![$($expecteds),+],
+            stringify!($actual),
+            $crate::internal::source_location::SourceLocation::new(file!(), line!(), column!()),
+        )
+    };
+    ($actual:expr, $expected:expr) => {
+        $crate::assertions::internal::check_matcher(
+            &$actual,
+            $expected,
+            stringify!($actual),
+            $crate::internal::source_location::SourceLocation::new(file!(), line!(), column!()),
+        )
+    };
+}
+
+/// Asserts that the given predicate applied to the given arguments returns
+/// true.
+///
+/// Similarly to [`verify_that`], this evaluates to a `Result` whose `Ok`
+/// variant indicates that the given predicate returned true and whose `Err`
+/// variant indicates that it returned false.
+///
+/// The failure message contains detailed information about the arguments. For
+/// example:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// fn equals_modulo(a: i32, b: i32, n: i32) -> bool { a % n == b % n }
+///
+/// # /* The attribute macro would prevent the function from being compiled in a doctest.
+/// #[test]
+/// # */
+/// fn test() -> Result<()> {
+///     let a = 1;
+///     let b = 7;
+///     let n = 5;
+///     verify_pred!(equals_modulo(a, b, n))?;
+///     Ok(())
+/// }
+/// # verify_that!(
+/// #     test(),
+/// #     err(displays_as(contains_substring("equals_modulo(a, b, n) was false with")))
+/// # ).unwrap();
+/// ```
+///
+/// This results in the following message:
+///
+/// ```text
+/// equals_modulo(a, b, n) was false with
+///   a = 1,
+///   b = 7,
+///   n = 5
+/// ```
+///
+/// The function passed to this macro must return `bool`. Each of the arguments
+/// must evaluate to a type implementing [`std::fmt::Debug`]. The debug output
+/// is used to construct the failure message.
+///
+/// The predicate can also be a method on a struct, e.g.:
+///
+/// ```ignore
+/// struct AStruct {};
+///
+/// impl AStruct {
+///   fn equals_modulo(...) {...}
+/// }
+///
+/// verify_pred!((AStruct {}).equals_modulo(a, b, n))?;
+/// ```
+///
+/// **Warning:** This macro assumes that the arguments passed to the predicate
+/// are either *variables* or *calls to pure functions*. If two subsequent
+/// invocations to any of the expresssions passed as arguments result in
+/// different values, then the output message of a test failure will deviate
+/// from the values actually passed to the predicate. For this reason, *always
+/// assign the outputs of non-pure functions to variables before using them in
+/// this macro. For example:
+///
+/// ```ignore
+/// let output = generate_random_number();  // Assigned outside of verify_pred.
+/// verify_pred!(is_sufficiently_random(output))?;
+/// ```
+#[macro_export]
+macro_rules! verify_pred {
+    ([$($predicate:tt)*]($($arg:tt),* $(,)?)) => {
+        if !$($predicate)*($($arg),*) {
+            $crate::assertions::internal::report_failed_predicate(
+                concat!(stringify!($($predicate)*), stringify!(($($arg),*))),
+                vec![$((format!(concat!(stringify!($arg), " = {:?}"), $arg))),*],
+                $crate::internal::source_location::SourceLocation::new(
+                    file!(),
+                    line!(),
+                    column!(),
+                ),
+            )
+        } else {
+            Ok(())
+        }
+    };
+
+    ([$($predicate:tt)*] $first:tt $($rest:tt)*) => {
+        $crate::verify_pred!([$($predicate)* $first] $($rest)*)
+    };
+
+    ($first:tt $($rest:tt)*) => {
+        $crate::verify_pred!([$first] $($rest)*)
+    };
+}
+
+/// Evaluates to a `Result` which contains an `Err` variant with the given test
+/// failure message.
+///
+/// This can be used to force the test to fail if its execution reaches a
+/// particular point. For example:
+///
+/// ```ignore
+/// match some_value {
+///     ExpectedVariant => {...}
+///     UnwantedVaraint => {
+///         fail!("This thing which should not happen happened anyway")?;
+///     }
+/// }
+/// ```
+///
+/// One may include formatted arguments in the failure message:
+///
+/// ```ignore
+/// match some_value {
+///     ExpectedVariant => {...}
+///     UnwantedVaraint => {
+///         fail!("This thing which should not happen happened anyway: {}", some_value)?;
+///     }
+/// }
+/// ```
+///
+/// One may also omit the message, in which case the test failure message will
+/// be generic:
+///
+/// ```ignore
+/// match some_value {
+///     ExpectedVariant => {...}
+///     UnwantedVaraint => {
+///         fail!()?;
+///     }
+/// }
+/// ```
+///
+/// Unlike `panic!` but analogously to [`verify_that`] and [`verify_pred`], this
+/// macro has no effect on the flow of control but instead returns a `Result`
+/// which must be handled by the invoking function. This can be done with the
+/// question mark operator (as above) or the method
+/// [`and_log_failure`](crate::GoogleTestSupport::and_log_failure).
+#[macro_export]
+macro_rules! fail {
+    ($($message:expr),+) => {{
+        // We wrap this in a function so that we can annotate it with the must_use attribute.
+        // must_use on expressions is still experimental.
+        #[must_use = "The assertion result must be evaluated to affect the test result."]
+        fn create_fail_result(message: String) -> $crate::Result<()> {
+            Err($crate::internal::test_outcome::TestAssertionFailure::create(format!(
+                "{}\n{}",
+                message,
+                $crate::internal::source_location::SourceLocation::new(
+                    file!(),
+                    line!(),
+                    column!(),
+                ),
+            )))
+        }
+        create_fail_result(format!($($message),*))
+    }};
+
+    () => { fail!("Test failed") };
+}
+
+/// Matches the given value against the given matcher, panicking if it does not
+/// match.
+///
+/// This is analogous to assertions in most Rust test libraries, where a failed
+/// assertion causes a panic.
+///
+/// **Note for users of [GoogleTest for C++](http://google.github.io/googletest/):**
+/// This differs from the `ASSERT_THAT` macro in that it panics rather
+/// than triggering an early return from the invoking function. To get behaviour
+/// equivalent to `ASSERT_THAT`, use [`verify_that!`] with the `?` operator.
+#[macro_export]
+macro_rules! assert_that {
+    ($actual:expr, $expected:expr) => {
+        match $crate::verify_that!($actual, $expected) {
+            Ok(_) => {}
+            Err(e) => {
+                // The extra newline before the assertion failure message makes the failure a
+                // bit easier to read when there's some generic boilerplate from the panic.
+                panic!("\n{}", e);
+            }
+        }
+    };
+}
+
+/// Asserts that the given predicate applied to the given arguments returns
+/// true, panicking if it does not.
+///
+/// **Note for users of [GoogleTest for C++](http://google.github.io/googletest/):**
+/// This differs from the `ASSERT_PRED*` family of macros in that it panics
+/// rather than triggering an early return from the invoking function. To get
+/// behaviour equivalent to `ASSERT_PRED*`, use [`verify_pred!`] with the `?`
+/// operator.
+#[macro_export]
+macro_rules! assert_pred {
+    ($($content:tt)*) => {
+        match $crate::verify_pred!($($content)*) {
+            Ok(_) => {}
+            Err(e) => {
+                // The extra newline before the assertion failure message makes the failure a
+                // bit easier to read when there's some generic boilerplate from the panic.
+                panic!("\n{}", e);
+            }
+        }
+    };
+}
+
+/// Matches the given value against the given matcher, marking the test as
+/// failed but continuing execution if it does not match.
+///
+/// This is a *non-fatal* assertion: the test continues
+/// execution in the event of assertion failure.
+///
+/// This can only be invoked inside tests with the
+/// [`googletest::test`][crate::test] attribute. The assertion must
+/// occur in the same thread as that running the test itself.
+///
+/// Invoking this macro is equivalent to using
+/// [`and_log_failure`](crate::GoogleTestSupport::and_log_failure) as follows:
+///
+/// ```ignore
+/// verify_that!(actual, expected).and_log_failure()
+/// ```
+#[macro_export]
+macro_rules! expect_that {
+    ($actual:expr, $expected:expr) => {{
+        use $crate::GoogleTestSupport;
+        $crate::verify_that!($actual, $expected).and_log_failure();
+    }};
+}
+
+/// Asserts that the given predicate applied to the given arguments returns
+/// true, failing the test but continuing execution if not.
+///
+/// This is a *non-fatal* predicate assertion: the test
+/// continues execution in the event of assertion failure.
+///
+/// This can only be invoked inside tests with the
+/// [`googletest::test`][crate::test] attribute. The assertion must
+/// occur in the same thread as that running the test itself.
+///
+/// Invoking this macro is equivalent to using
+/// [`and_log_failure`](crate::GoogleTestSupport::and_log_failure) as follows:
+///
+/// ```ignore
+/// verify_pred!(predicate(...)).and_log_failure()
+/// ```
+#[macro_export]
+macro_rules! expect_pred {
+    ($($content:tt)*) => {{
+        use $crate::GoogleTestSupport;
+        $crate::verify_pred!($($content)*).and_log_failure();
+    }};
+}
+
+/// Functions for use only by the procedural macros in this module.
+///
+/// **For internal use only. API stablility is not guaranteed!**
+#[doc(hidden)]
+pub mod internal {
+    use crate::{
+        internal::{source_location::SourceLocation, test_outcome::TestAssertionFailure},
+        matcher::{create_assertion_failure, Matcher, MatcherResult},
+    };
+    use std::fmt::Debug;
+
+    /// Checks whether the matcher `expected` matches the value `actual`, adding
+    /// a test failure report if it does not match.
+    ///
+    /// Returns `Ok(())` if the value matches and `Err(())` if it does not
+    /// match.
+    ///
+    /// **For internal use only. API stablility is not guaranteed!**
+    #[must_use = "The assertion result must be evaluated to affect the test result."]
+    pub fn check_matcher<T: Debug + ?Sized>(
+        actual: &T,
+        expected: impl Matcher<ActualT = T>,
+        actual_expr: &'static str,
+        source_location: SourceLocation,
+    ) -> Result<(), TestAssertionFailure> {
+        match expected.matches(actual) {
+            MatcherResult::Match => Ok(()),
+            MatcherResult::NoMatch => {
+                Err(create_assertion_failure(&expected, actual, actual_expr, source_location))
+            }
+        }
+    }
+
+    /// Constructs a `Result::Err(TestAssertionFailure)` for a predicate failure
+    /// as produced by the macro [`crate::verify_pred`].
+    ///
+    /// This intended only for use by the macro [`crate::verify_pred`].
+    ///
+    /// **For internal use only. API stablility is not guaranteed!**
+    #[must_use = "The assertion result must be evaluated to affect the test result."]
+    pub fn report_failed_predicate(
+        actual_expr: &'static str,
+        formatted_arguments: Vec<String>,
+        source_location: SourceLocation,
+    ) -> Result<(), TestAssertionFailure> {
+        Err(TestAssertionFailure::create(format!(
+            "{} was false with\n  {}\n{}",
+            actual_expr,
+            formatted_arguments.join(",\n  "),
+            source_location,
+        )))
+    }
+}
diff --git a/src/internal/mod.rs b/src/internal/mod.rs
new file mode 100644
index 0000000..2f37418
--- /dev/null
+++ b/src/internal/mod.rs
@@ -0,0 +1,18 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#![doc(hidden)]
+
+pub mod source_location;
+pub mod test_outcome;
diff --git a/src/internal/source_location.rs b/src/internal/source_location.rs
new file mode 100644
index 0000000..4520c92
--- /dev/null
+++ b/src/internal/source_location.rs
@@ -0,0 +1,44 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use std::fmt::{Display, Error, Formatter};
+
+/// Encapsulates a location in source code.
+///
+/// This is intended to report the location of an assertion which failed to
+/// stdout.
+///
+/// **For internal use only. API stablility is not guaranteed!**
+#[doc(hidden)]
+pub struct SourceLocation {
+    file: &'static str,
+    line: u32,
+    column: u32,
+}
+
+impl SourceLocation {
+    /// Constructs a new [`SourceLocation`].
+    ///
+    /// **For internal use only. API stablility is not guaranteed!**
+    #[doc(hidden)]
+    pub fn new(file: &'static str, line: u32, column: u32) -> Self {
+        Self { file, line, column }
+    }
+}
+
+impl Display for SourceLocation {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
+        write!(f, "  at {}:{}:{}", self.file, self.line, self.column)
+    }
+}
diff --git a/src/internal/test_outcome.rs b/src/internal/test_outcome.rs
new file mode 100644
index 0000000..2171cc7
--- /dev/null
+++ b/src/internal/test_outcome.rs
@@ -0,0 +1,211 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use std::cell::{RefCell, RefMut};
+use std::fmt::{Debug, Display, Error, Formatter};
+use std::thread_local;
+
+/// The outcome hitherto of running a test.
+///
+/// This is kept as a running record as the test progresses. One can access it
+/// with `TestOutcome::with_current_test_outcome`.
+///
+/// **For internal use only. API stablility is not guaranteed!**
+#[doc(hidden)]
+pub enum TestOutcome {
+    /// The test ran or is currently running and no assertions have failed.
+    Success,
+    /// The test ran or is currently running and at least one assertion has
+    /// failed.
+    Failure,
+}
+
+thread_local! {
+    static CURRENT_TEST_OUTCOME: RefCell<Option<TestOutcome>> = RefCell::new(None);
+}
+
+impl TestOutcome {
+    /// Resets the current test's [`TestOutcome`].
+    ///
+    /// This is intended only for use by the attribute macro
+    /// `#[googletest::test]`.
+    ///
+    /// **For internal use only. API stablility is not guaranteed!**
+    #[doc(hidden)]
+    pub fn init_current_test_outcome() {
+        Self::with_current_test_outcome(|mut current_test_outcome| {
+            *current_test_outcome = Some(TestOutcome::Success);
+        })
+    }
+
+    /// Evaluates the current test's [`TestOutcome`], producing a suitable
+    /// `Result`.
+    ///
+    /// The parameter `result` is the value returned by the test function
+    /// itself. This returns `Result::Err` with a `Display`-formatted string of
+    /// the error if `result` is `Result::Err`.
+    ///
+    /// Otherwise, this returns `Result::Err` precisely when a test failure has
+    /// been recorded with
+    /// [`and_log_failure`](crate::GoogleTestSupport::and_log_failure).
+    ///
+    /// **For internal use only. API stablility is not guaranteed!**
+    #[doc(hidden)]
+    pub fn close_current_test_outcome<E: Display>(
+        inner_result: Result<(), E>,
+    ) -> Result<(), TestFailure> {
+        TestOutcome::with_current_test_outcome(|mut outcome| {
+            let outer_result = match &*outcome {
+                Some(TestOutcome::Success) => match inner_result {
+                    Ok(()) => Ok(()),
+                    Err(_) => Err(TestFailure),
+                },
+                Some(TestOutcome::Failure) => Err(TestFailure),
+                None => {
+                    panic!("No test context found. This indicates a bug in GoogleTest.")
+                }
+            };
+            if let Err(fatal_assertion_failure) = inner_result {
+                println!("{fatal_assertion_failure}");
+            }
+            *outcome = None;
+            outer_result
+        })
+    }
+
+    /// Returns a `Result` corresponding to the outcome of the currently running
+    /// test.
+    pub(crate) fn get_current_test_outcome() -> Result<(), TestAssertionFailure> {
+        TestOutcome::with_current_test_outcome(|mut outcome| {
+            let outcome = outcome
+                .as_mut()
+                .expect("No test context found. This indicates a bug in GoogleTest.");
+            match outcome {
+                TestOutcome::Success => Ok(()),
+                TestOutcome::Failure => Err(TestAssertionFailure::create("Test failed".into())),
+            }
+        })
+    }
+
+    /// Records that the currently running test has failed.
+    fn fail_current_test() {
+        TestOutcome::with_current_test_outcome(|mut outcome| {
+            let outcome = outcome
+                .as_mut()
+                .expect("No test context found. This indicates a bug in GoogleTest.");
+            *outcome = TestOutcome::Failure;
+        })
+    }
+
+    /// Runs `action` with the [`TestOutcome`] for the currently running test.
+    ///
+    /// This is primarily intended for use by assertion macros like
+    /// `expect_that!`.
+    fn with_current_test_outcome<T>(action: impl FnOnce(RefMut<Option<TestOutcome>>) -> T) -> T {
+        CURRENT_TEST_OUTCOME.with(|current_test_outcome| action(current_test_outcome.borrow_mut()))
+    }
+
+    /// Ensure that there is a test context present and panic if there is not.
+    pub(crate) fn ensure_text_context_present() {
+        TestOutcome::with_current_test_outcome(|outcome| {
+            outcome.as_ref().expect(
+                "
+No test context found.
+ * Did you annotate the test with googletest::test?
+ * Is the assertion running in the original test thread?
+",
+            );
+        })
+    }
+}
+
+/// A marking struct indicating that a test has failed.
+///
+/// This exists to implement the [Error][std::error::Error] trait. It displays
+/// to a message indicating that the actual test assertion failure messages are
+/// in the text above.
+pub struct TestFailure;
+
+impl std::error::Error for TestFailure {}
+
+impl std::fmt::Debug for TestFailure {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
+        writeln!(f, "See failure output above")?;
+        Ok(())
+    }
+}
+
+impl std::fmt::Display for TestFailure {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
+        writeln!(f, "See failure output above")?;
+        Ok(())
+    }
+}
+
+/// A report that a single test assertion failed.
+///
+/// **For internal use only. API stablility is not guaranteed!**
+#[doc(hidden)]
+#[derive(Clone)]
+pub struct TestAssertionFailure {
+    /// A human-readable formatted string describing the error.
+    pub description: String,
+    pub custom_message: Option<String>,
+}
+
+impl TestAssertionFailure {
+    /// Creates a new instance with the given `description`.
+    ///
+    /// **For internal use only. API stablility is not guaranteed!**
+    pub fn create(description: String) -> Self {
+        Self { description, custom_message: None }
+    }
+
+    pub(crate) fn log(&self) {
+        TestOutcome::fail_current_test();
+        print!("{}", self);
+    }
+}
+
+impl Display for TestAssertionFailure {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
+        writeln!(f, "{}", self.description)?;
+        if let Some(custom_message) = &self.custom_message {
+            writeln!(f, "{}", custom_message)?;
+        }
+        Ok(())
+    }
+}
+
+// The standard Rust test harness outputs the TestAssertionFailure with the
+// Debug trait. We want the output to be formatted, so we use a custom Debug
+// implementation which defers to Display.
+impl Debug for TestAssertionFailure {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
+        Display::fmt(self, f)
+    }
+}
+
+impl<T: std::error::Error> From<T> for TestAssertionFailure {
+    fn from(value: T) -> Self {
+        TestAssertionFailure::create(format!("{value}"))
+    }
+}
+
+#[cfg(feature = "proptest")]
+impl From<TestAssertionFailure> for proptest::test_runner::TestCaseError {
+    fn from(value: TestAssertionFailure) -> Self {
+        proptest::test_runner::TestCaseError::Fail(format!("{value}").into())
+    }
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..cec6da0
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,273 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#![doc = include_str!("../crate_docs.md")]
+
+extern crate googletest_macro;
+
+#[cfg(test)]
+extern crate quickcheck;
+
+#[macro_use]
+pub mod assertions;
+pub mod internal;
+pub mod matcher;
+pub mod matcher_support;
+pub mod matchers;
+
+/// Re-exports of the symbols in this crate which are most likely to be used.
+///
+/// This includes:
+///  * All assertion macros,
+///  * Traits and type definitions normally used by tests, and
+///  * All built-in matchers.
+///
+/// Typically, one imports everything in the prelude in one's test module:
+///
+/// ```
+/// mod tests {
+///     use googletest::prelude::*;
+/// }
+/// ```
+pub mod prelude {
+    pub use super::matcher::Matcher;
+    pub use super::matchers::*;
+    pub use super::verify_current_test_outcome;
+    pub use super::GoogleTestSupport;
+    pub use super::IntoTestResult;
+    pub use super::Result;
+    // Assert macros
+    pub use super::{assert_that, expect_pred, expect_that, fail, verify_pred, verify_that};
+    // Matcher macros
+    pub use super::{
+        all, any, contains_each, elements_are, field, is_contained_in, matches_pattern, pat,
+        pointwise, property, unordered_elements_are,
+    };
+}
+
+pub use googletest_macro::test;
+
+use internal::test_outcome::{TestAssertionFailure, TestOutcome};
+
+/// A `Result` whose `Err` variant indicates a test failure.
+///
+/// All test functions should return `Result<()>`.
+///
+/// This can be used with subroutines which may cause the test to fatally fail
+/// and which return some value needed by the caller. For example:
+///
+/// ```ignore
+/// fn load_file_content_as_string() -> Result<String> {
+///     let file_stream = load_file().err_to_test_failure()?;
+///     Ok(file_stream.to_string())
+/// }
+/// ```
+///
+/// The `Err` variant contains a [`TestAssertionFailure`] which carries the data
+/// of the (fatal) assertion failure which generated this result. Non-fatal
+/// assertion failures, which log the failure and report the test as having
+/// failed but allow it to continue running, are not encoded in this type.
+pub type Result<T> = std::result::Result<T, TestAssertionFailure>;
+
+/// Returns a [`Result`] corresponding to the outcome of the currently running
+/// test.
+///
+/// This returns `Result::Err` precisely if the current test has recorded at
+/// least one test assertion failure via [`expect_that!`][crate::expect_that],
+/// [`expect_pred!`][crate::expect_pred], or
+/// [`GoogleTestSupport::and_log_failure`]. It can be used in concert with the
+/// `?` operator to continue execution of the test conditionally on there not
+/// having been any failure yet.
+///
+/// This requires the use of the [`#[googletest::test]`][crate::test] attribute
+/// macro.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # /* Make sure this also compiles as a doctest.
+/// #[googletest::test]
+/// # */
+/// # fn foo() -> u32 { 1 }
+/// # fn bar() -> u32 { 2 }
+/// fn should_fail_and_not_execute_last_assertion() -> Result<()> {
+/// #   googletest::internal::test_outcome::TestOutcome::init_current_test_outcome();
+///     expect_that!(foo(), eq(2));     // May fail, but will not abort the test.
+///     expect_that!(bar(), gt(1));     // May fail, but will not abort the test.
+///     verify_current_test_outcome()?; // Aborts the test if one of the previous assertions failed.
+///     verify_that!(foo(), gt(0))      // Does not execute if the line above aborts.
+/// }
+/// # verify_that!(should_fail_and_not_execute_last_assertion(), err(displays_as(contains_substring("Test failed")))).unwrap();
+/// ```
+pub fn verify_current_test_outcome() -> Result<()> {
+    TestOutcome::get_current_test_outcome()
+}
+
+/// Adds to `Result` support for GoogleTest Rust functionality.
+pub trait GoogleTestSupport {
+    /// If `self` is a `Result::Err`, writes to `stdout` a failure report
+    /// and marks the test failed. Otherwise, does nothing.
+    ///
+    /// This can be used for non-fatal test assertions, for example:
+    ///
+    /// ```
+    /// # use googletest::prelude::*;
+    /// # use googletest::internal::test_outcome::TestOutcome;
+    /// # TestOutcome::init_current_test_outcome();
+    /// let actual = 42;
+    /// verify_that!(actual, eq(42)).and_log_failure();
+    ///                                  // Test still passing; nothing happens
+    /// verify_that!(actual, eq(10)).and_log_failure();
+    ///                          // Test now fails and failure output to stdout
+    /// verify_that!(actual, eq(100)).and_log_failure();
+    ///               // Test still fails and new failure also output to stdout
+    /// # TestOutcome::close_current_test_outcome::<&str>(Ok(())).unwrap_err();
+    /// ```
+    fn and_log_failure(self);
+
+    /// Adds `message` to the logged failure message if `self` is a
+    /// `Result::Err`. Otherwise, does nothing.
+    ///
+    /// If this method is called more than once, only `message` from the last
+    /// invocation is output.
+    ///
+    /// For example:
+    ///
+    /// ```
+    /// # use googletest::prelude::*;
+    /// # fn should_fail() -> Result<()> {
+    /// let actual = 0;
+    /// verify_that!(actual, eq(42)).failure_message("Actual was wrong!")?;
+    /// # Ok(())
+    /// # }
+    /// # verify_that!(should_fail(), err(displays_as(contains_substring("Actual was wrong"))))
+    /// #     .unwrap();
+    /// ```
+    ///
+    /// results in the following failure message:
+    ///
+    /// ```text
+    /// Expected: actual equal to 42
+    ///   but was: 0
+    /// Actual was wrong!
+    /// ```
+    ///
+    /// One can pass a `String` too:
+    ///
+    /// ```
+    /// # use googletest::prelude::*;
+    /// # fn should_fail() -> Result<()> {
+    /// let actual = 0;
+    /// verify_that!(actual, eq(42))
+    ///    .failure_message(format!("Actual {} was wrong!", actual))?;
+    /// # Ok(())
+    /// # }
+    /// # verify_that!(should_fail(), err(displays_as(contains_substring("Actual 0 was wrong"))))
+    /// #     .unwrap();
+    /// ```
+    ///
+    /// However, consider using [`GoogleTestSupport::with_failure_message`]
+    /// instead in that case to avoid unnecessary memory allocation when the
+    /// message is not needed.
+    fn failure_message(self, message: impl Into<String>) -> Self;
+
+    /// Adds the output of the closure `provider` to the logged failure message
+    /// if `self` is a `Result::Err`. Otherwise, does nothing.
+    ///
+    /// This is analogous to [`GoogleTestSupport::failure_message`] but
+    /// only executes the closure `provider` if it actually produces the
+    /// message, thus saving possible memory allocation.
+    ///
+    /// ```
+    /// # use googletest::prelude::*;
+    /// # fn should_fail() -> Result<()> {
+    /// let actual = 0;
+    /// verify_that!(actual, eq(42))
+    ///    .with_failure_message(|| format!("Actual {} was wrong!", actual))?;
+    /// # Ok(())
+    /// # }
+    /// # verify_that!(should_fail(), err(displays_as(contains_substring("Actual 0 was wrong"))))
+    /// #     .unwrap();
+    /// ```
+    fn with_failure_message(self, provider: impl FnOnce() -> String) -> Self;
+}
+
+impl<T> GoogleTestSupport for std::result::Result<T, TestAssertionFailure> {
+    fn and_log_failure(self) {
+        TestOutcome::ensure_text_context_present();
+        if let Err(failure) = self {
+            failure.log();
+        }
+    }
+
+    fn failure_message(mut self, message: impl Into<String>) -> Self {
+        if let Err(ref mut failure) = self {
+            failure.custom_message = Some(message.into());
+        }
+        self
+    }
+
+    fn with_failure_message(mut self, provider: impl FnOnce() -> String) -> Self {
+        if let Err(ref mut failure) = self {
+            failure.custom_message = Some(provider());
+        }
+        self
+    }
+}
+
+/// Provides an extension method for converting an arbitrary type into a
+/// [`Result`].
+///
+/// A type can implement this trait to provide an easy way to return immediately
+/// from a test in conjunction with the `?` operator. This is useful for
+/// [`Result`][std::result::Result] types whose `Result::Err` variant does not
+/// implement [`std::error::Error`].
+///
+/// There is an implementation of this trait for [`anyhow::Error`] (which does
+/// not implement `std::error::Error`) when the `anyhow` feature is enabled.
+/// Importing this trait allows one to easily map [`anyhow::Error`] to a test
+/// failure:
+///
+/// ```ignore
+/// #[test]
+/// fn should_work() -> Result<()> {
+///     let value = something_which_can_fail().into_test_result()?;
+///     ...
+/// }
+///
+/// fn something_which_can_fail() -> anyhow::Result<...> { ... }
+/// ```
+pub trait IntoTestResult<T> {
+    /// Converts this instance into a [`Result`].
+    ///
+    /// Typically, the `Self` type is itself a [`std::result::Result`]. This
+    /// method should then map the `Err` variant to a [`TestAssertionFailure`]
+    /// and leave the `Ok` variant unchanged.
+    fn into_test_result(self) -> Result<T>;
+}
+
+#[cfg(feature = "anyhow")]
+impl<T> IntoTestResult<T> for std::result::Result<T, anyhow::Error> {
+    fn into_test_result(self) -> std::result::Result<T, TestAssertionFailure> {
+        self.map_err(|e| TestAssertionFailure::create(format!("{e}")))
+    }
+}
+
+#[cfg(feature = "proptest")]
+impl<OkT, CaseT: std::fmt::Debug> IntoTestResult<OkT>
+    for std::result::Result<OkT, proptest::test_runner::TestError<CaseT>>
+{
+    fn into_test_result(self) -> std::result::Result<OkT, TestAssertionFailure> {
+        self.map_err(|e| TestAssertionFailure::create(format!("{e}")))
+    }
+}
diff --git a/src/matcher.rs b/src/matcher.rs
new file mode 100644
index 0000000..83a4475
--- /dev/null
+++ b/src/matcher.rs
@@ -0,0 +1,265 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! The components required to implement matchers.
+
+use crate::internal::source_location::SourceLocation;
+use crate::internal::test_outcome::TestAssertionFailure;
+use crate::matchers::conjunction_matcher::ConjunctionMatcher;
+use crate::matchers::disjunction_matcher::DisjunctionMatcher;
+use std::fmt::Debug;
+
+/// An interface for checking an arbitrary condition on a datum.
+pub trait Matcher {
+    /// The type against which this matcher matches.
+    type ActualT: Debug + ?Sized;
+
+    /// Returns whether the condition matches the datum `actual`.
+    ///
+    /// The trait implementation defines what it means to "match". Often the
+    /// matching condition is based on data stored in the matcher. For example,
+    /// `eq` matches when its stored expected value is equal (in the sense of
+    /// the `==` operator) to the value `actual`.
+    fn matches(&self, actual: &Self::ActualT) -> MatcherResult;
+
+    /// Returns a description of `self` or a negative description if
+    /// `matcher_result` is `DoesNotMatch`.
+    ///
+    /// The function should print a verb phrase that describes the property a
+    /// value matching, respectively not matching, this matcher should have.
+    /// The subject of the verb phrase is the value being matched.
+    ///
+    /// The output appears next to `Expected` in an assertion failure message.
+    /// For example:
+    ///
+    /// ```text
+    /// Value of: ...
+    /// Expected: is equal to 7
+    ///           ^^^^^^^^^^^^^
+    /// Actual: ...
+    /// ```
+    ///
+    /// When the matcher contains one or more inner matchers, the implementation
+    /// should invoke [`Self::describe`] on the inner matchers to complete the
+    /// description. It should place the inner description at a point where a
+    /// verb phrase would fit. For example, the matcher
+    /// [`some`][crate::matchers::some] implements `describe` as follows:
+    ///
+    /// ```ignore
+    /// fn describe(&self, matcher_result: MatcherResult) -> String {
+    ///     match matcher_result {
+    ///         MatcherResult::Matches => {
+    ///             format!("has a value which {}", self.inner.describe(MatcherResult::Matches))
+    ///               //  Inner matcher invocation: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+    ///         }
+    ///         MatcherResult::DoesNotMatch => {...} // Similar to the above
+    ///     }
+    /// }
+    /// ```
+    ///
+    /// The output expectation differs from that of
+    /// [`explain_match`][Self::explain_match] in that it is a verb phrase
+    /// (beginning with a verb like "is") rather than a relative clause
+    /// (beginning with "which" or "whose"). This difference is because the
+    /// output of `explain_match` is always used adjectivally to describe the
+    /// actual value, while `describe` is used in contexts where a relative
+    /// clause would not make sense.
+    fn describe(&self, matcher_result: MatcherResult) -> String;
+
+    /// Prepares a [`String`] describing how the expected value
+    /// encoded in this instance matches or does not match the given value
+    /// `actual`.
+    ///
+    /// This should be in the form of a relative clause, i.e. something starting
+    /// with a relative pronoun such as "which" or "whose". It will appear next
+    /// to the actual value in an assertion failure. For example:
+    ///
+    /// ```text
+    /// Value of: ...
+    /// Expected: ...
+    /// Actual: ["Something"], which does not contain "Something else"
+    ///                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+    /// ```
+    ///
+    /// The default implementation relies on [`describe`][Self::describe]. Thus
+    /// it does not make any use of the actual value itself, but rather only
+    /// whether the value is matched.
+    ///
+    /// Override the default implementation to provide additional context on why
+    /// a particular value matched or did not match. For example, the
+    /// [`container_eq`][crate::matchers::container_eq] matcher displays
+    /// information on which elements of the actual value were not present in
+    /// the expected value and vice versa.
+    ///
+    /// This implementation should be overridden in any matcher which contains
+    /// one or more inner matchers. The implementation should invoke
+    /// `explain_match` on the inner matchers, so that the generated match
+    /// explanation also reflects their implementation. Without this, the match
+    /// explanation of the inner matchers will not be able to make use of the
+    /// actual value at all.
+    ///
+    /// For example, the `explain_match` implementation of the matcher
+    /// [`points_to`][crate::matchers::points_to] defers immediately to the
+    /// inner matcher and appears as follows:
+    ///
+    /// ```ignore
+    /// fn explain_match(&self, actual: &Self::ActualT) -> String {
+    ///     self.expected.explain_match(actual.deref())
+    /// }
+    /// ```
+    ///
+    /// The matcher can also provide some additional context before deferring to
+    /// an inner matcher. In that case it should invoke `explain_match` on the
+    /// inner matcher at a point where a relative clause would fit. For example:
+    ///
+    /// ```ignore
+    /// fn explain_match(&self, actual: &Self::ActualT) -> String {
+    ///     format!("which points to a value {}", self.expected.explain_match(actual.deref()))
+    /// }
+    /// ```
+    fn explain_match(&self, actual: &Self::ActualT) -> String {
+        format!("which {}", self.describe(self.matches(actual)))
+    }
+
+    /// Constructs a matcher that matches both `self` and `right`.
+    ///
+    /// ```
+    /// # use googletest::prelude::*;
+    /// # fn should_pass() -> Result<()> {
+    /// verify_that!("A string", starts_with("A").and(ends_with("string")))?; // Passes
+    /// #     Ok(())
+    /// # }
+    /// # fn should_fail_1() -> Result<()> {
+    /// verify_that!("A string", starts_with("Another").and(ends_with("string")))?; // Fails
+    /// #     Ok(())
+    /// # }
+    /// # fn should_fail_2() -> Result<()> {
+    /// verify_that!("A string", starts_with("A").and(ends_with("non-string")))?; // Fails
+    /// #     Ok(())
+    /// # }
+    /// # should_pass().unwrap();
+    /// # should_fail_1().unwrap_err();
+    /// # should_fail_2().unwrap_err();
+    /// ```
+    // TODO(b/264518763): Replace the return type with impl Matcher and reduce
+    // visibility of ConjunctionMatcher once impl in return position in trait
+    // methods is stable.
+    fn and<Right: Matcher<ActualT = Self::ActualT>>(
+        self,
+        right: Right,
+    ) -> ConjunctionMatcher<Self, Right>
+    where
+        Self: Sized,
+    {
+        ConjunctionMatcher::new(self, right)
+    }
+
+    /// Constructs a matcher that matches when at least one of `self` or `right`
+    /// matches the input.
+    ///
+    /// ```
+    /// # use googletest::prelude::*;
+    /// # fn should_pass() -> Result<()> {
+    /// verify_that!(10, eq(2).or(ge(5)))?;  // Passes
+    /// verify_that!(10, eq(2).or(eq(5)).or(ge(9)))?;  // Passes
+    /// #     Ok(())
+    /// # }
+    /// # fn should_fail() -> Result<()> {
+    /// verify_that!(10, eq(2).or(ge(15)))?; // Fails
+    /// #     Ok(())
+    /// # }
+    /// # should_pass().unwrap();
+    /// # should_fail().unwrap_err();
+    /// ```
+    // TODO(b/264518763): Replace the return type with impl Matcher and reduce
+    // visibility of DisjunctionMatcher once impl in return position in trait
+    // methods is stable.
+    fn or<Right: Matcher<ActualT = Self::ActualT>>(
+        self,
+        right: Right,
+    ) -> DisjunctionMatcher<Self, Right>
+    where
+        Self: Sized,
+    {
+        DisjunctionMatcher::new(self, right)
+    }
+}
+
+/// Any actual value whose debug length is greater than this value will be
+/// pretty-printed. Otherwise, it will have normal debug output formatting.
+const PRETTY_PRINT_LENGTH_THRESHOLD: usize = 60;
+
+/// Constructs a [`TestAssertionFailure`] reporting that the given `matcher`
+/// does not match the value `actual`.
+///
+/// The parameter `actual_expr` contains the expression which was evaluated to
+/// obtain `actual`.
+pub(crate) fn create_assertion_failure<T: Debug + ?Sized>(
+    matcher: &impl Matcher<ActualT = T>,
+    actual: &T,
+    actual_expr: &'static str,
+    source_location: SourceLocation,
+) -> TestAssertionFailure {
+    let actual_formatted = format!("{actual:?}");
+    let actual_formatted = if actual_formatted.len() > PRETTY_PRINT_LENGTH_THRESHOLD {
+        format!("{actual:#?}")
+    } else {
+        actual_formatted
+    };
+    TestAssertionFailure::create(format!(
+        "\
+Value of: {actual_expr}
+Expected: {}
+Actual: {actual_formatted},
+  {}
+{source_location}",
+        matcher.describe(MatcherResult::Match),
+        matcher.explain_match(actual),
+    ))
+}
+
+/// The result of applying a [`Matcher`] on an actual value.
+#[derive(Debug, PartialEq, Clone, Copy)]
+pub enum MatcherResult {
+    /// The actual value matches according to the [`Matcher`] definition.
+    Match,
+    /// The actual value does not match according to the [`Matcher`] definition.
+    NoMatch,
+}
+
+impl From<bool> for MatcherResult {
+    fn from(b: bool) -> Self {
+        if b { MatcherResult::Match } else { MatcherResult::NoMatch }
+    }
+}
+
+impl From<MatcherResult> for bool {
+    fn from(matcher_result: MatcherResult) -> Self {
+        matcher_result.is_match()
+    }
+}
+
+impl MatcherResult {
+    /// Returns `true` if `self` is [`MatcherResult::Match`], otherwise
+    /// `false`.
+    pub fn is_match(self) -> bool {
+        matches!(self, MatcherResult::Match)
+    }
+
+    /// Returns `true` if `self` is [`MatcherResult::NoMatch`], otherwise
+    /// `false`.
+    pub fn is_no_match(self) -> bool {
+        matches!(self, MatcherResult::NoMatch)
+    }
+}
diff --git a/src/matcher_support/count_elements.rs b/src/matcher_support/count_elements.rs
new file mode 100644
index 0000000..662dcc9
--- /dev/null
+++ b/src/matcher_support/count_elements.rs
@@ -0,0 +1,32 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/// Counts the number of elements in `value`.
+///
+/// This uses [`Iterator::size_hint`] when that function returns an
+/// unambiguous answer, i.e., the upper bound exists and the lower and upper
+/// bounds agree. Otherwise it iterates through `value` and counts the
+/// elements.
+pub(crate) fn count_elements<ContainerT: ?Sized>(value: &ContainerT) -> usize
+where
+    for<'b> &'b ContainerT: IntoIterator,
+{
+    let iterator = value.into_iter();
+    if let (lower, Some(higher)) = iterator.size_hint() {
+        if lower == higher {
+            return lower;
+        }
+    }
+    iterator.count()
+}
diff --git a/src/matcher_support/description.rs b/src/matcher_support/description.rs
new file mode 100644
index 0000000..ce074e0
--- /dev/null
+++ b/src/matcher_support/description.rs
@@ -0,0 +1,410 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use std::fmt::{Display, Formatter, Result};
+
+/// Helper structure to build better output of
+/// [`Matcher::describe`][crate::matcher::Matcher::describe] and
+/// [`Matcher::explain_match`][crate::matcher::Matcher::explain_match]. This
+/// is especially useful with composed matchers and matchers over containers.
+///
+/// It provides simple operations to lazily format lists of strings.
+///
+/// Usage:
+/// ```ignore
+/// let iter: impl Iterator<String> = ...
+/// format!("{}", iter.collect::<Description>().indent().bullet_list())
+/// ```
+///
+/// To construct a [`Description`], use `Iterator<Item=String>::collect()`.
+/// Each element of the collected iterator will be separated by a
+/// newline when displayed. The elements may be multi-line, but they will
+/// nevertheless be indented consistently.
+///
+/// Note that a newline will only be added between each element, but not
+/// after the last element. This makes it simpler to keep
+/// [`Matcher::describe`][crate::matcher::Matcher::describe]
+/// and [`Matcher::explain_match`][crate::matcher::Matcher::explain_match]
+/// consistent with simpler [`Matchers`][crate::matcher::Matcher].
+///
+/// They can also be indented, enumerated and or
+/// bullet listed if [`Description::indent`], [`Description::enumerate`], or
+/// respectively [`Description::bullet_list`] has been called.
+#[derive(Debug)]
+pub struct Description {
+    elements: Vec<String>,
+    indent_mode: IndentMode,
+    list_style: ListStyle,
+}
+
+#[derive(Debug)]
+enum IndentMode {
+    NoIndent,
+    EveryLine,
+    AllExceptFirstLine,
+}
+
+#[derive(Debug)]
+enum ListStyle {
+    NoList,
+    Bullet,
+    Enumerate,
+}
+
+struct IndentationSizes {
+    first_line_indent: usize,
+    first_line_of_element_indent: usize,
+    enumeration_padding: usize,
+    other_line_indent: usize,
+}
+
+/// Number of space used to indent lines when no alignement is required.
+const INDENTATION_SIZE: usize = 2;
+
+impl Description {
+    /// Indents the lines in elements of this description.
+    ///
+    /// This operation will be performed lazily when [`self`] is displayed.
+    ///
+    /// This will indent every line inside each element.
+    ///
+    /// For example:
+    ///
+    /// ```
+    /// # use googletest::prelude::*;
+    /// # use googletest::matcher_support::description::Description;
+    /// let description = std::iter::once("A B C\nD E F".to_string()).collect::<Description>();
+    /// verify_that!(description.indent(), displays_as(eq("  A B C\n  D E F")))
+    /// # .unwrap();
+    /// ```
+    pub fn indent(self) -> Self {
+        Self { indent_mode: IndentMode::EveryLine, ..self }
+    }
+
+    /// Indents the lines in elements of this description except for the first
+    /// line.
+    ///
+    /// This is similar to [`Self::indent`] except that the first line is not
+    /// indented. This is useful when the first line has already been indented
+    /// in the output.
+    ///
+    /// For example:
+    ///
+    /// ```
+    /// # use googletest::prelude::*;
+    /// # use googletest::matcher_support::description::Description;
+    /// let description = std::iter::once("A B C\nD E F".to_string()).collect::<Description>();
+    /// verify_that!(description.indent_except_first_line(), displays_as(eq("A B C\n  D E F")))
+    /// # .unwrap();
+    /// ```
+    pub fn indent_except_first_line(self) -> Self {
+        Self { indent_mode: IndentMode::AllExceptFirstLine, ..self }
+    }
+
+    /// Bullet lists the elements of [`self`].
+    ///
+    /// This operation will be performed lazily when [`self`] is displayed.
+    ///
+    /// Note that this will only bullet list each element, not each line
+    /// in each element.
+    ///
+    /// For instance:
+    ///
+    /// ```
+    /// # use googletest::prelude::*;
+    /// # use googletest::matcher_support::description::Description;
+    /// let description = std::iter::once("A B C\nD E F".to_string()).collect::<Description>();
+    /// verify_that!(description.bullet_list(), displays_as(eq("* A B C\n  D E F")))
+    /// # .unwrap();
+    /// ```
+    pub fn bullet_list(self) -> Self {
+        Self { list_style: ListStyle::Bullet, ..self }
+    }
+
+    /// Enumerates the elements of [`self`].
+    ///
+    /// This operation will be performed lazily when [`self`] is displayed.
+    ///
+    /// Note that this will only enumerate each element, not each line in
+    /// each element.
+    ///
+    /// For instance:
+    ///
+    /// ```
+    /// # use googletest::prelude::*;
+    /// # use googletest::matcher_support::description::Description;
+    /// let description = std::iter::once("A B C\nD E F".to_string()).collect::<Description>();
+    /// verify_that!(description.enumerate(), displays_as(eq("0. A B C\n   D E F")))
+    /// # .unwrap();
+    /// ```
+    pub fn enumerate(self) -> Self {
+        Self { list_style: ListStyle::Enumerate, ..self }
+    }
+
+    /// Returns the length of elements.
+    pub fn len(&self) -> usize {
+        self.elements.len()
+    }
+
+    /// Returns whether the set of elements is empty.
+    pub fn is_empty(&self) -> bool {
+        self.elements.is_empty()
+    }
+
+    fn indentation_sizes(&self) -> IndentationSizes {
+        let first_line_indent =
+            if matches!(self.indent_mode, IndentMode::EveryLine) { INDENTATION_SIZE } else { 0 };
+        let first_line_of_element_indent =
+            if !matches!(self.indent_mode, IndentMode::NoIndent) { INDENTATION_SIZE } else { 0 };
+        // Number of digit of the last index. For instance, an array of length 13 will
+        // have 12 as last index (we start at 0), which have a digit size of 2.
+        let enumeration_padding = if self.elements.len() > 1 {
+            ((self.elements.len() - 1) as f64).log10().floor() as usize + 1
+        } else {
+            // Avoid negative logarithm when there is only 0 or 1 element.
+            1
+        };
+
+        let other_line_indent = first_line_of_element_indent
+            + match self.list_style {
+                ListStyle::NoList => 0,
+                ListStyle::Bullet => "* ".len(),
+                ListStyle::Enumerate => enumeration_padding + ". ".len(),
+            };
+        IndentationSizes {
+            first_line_indent,
+            first_line_of_element_indent,
+            enumeration_padding,
+            other_line_indent,
+        }
+    }
+}
+
+impl Display for Description {
+    fn fmt(&self, f: &mut Formatter) -> Result {
+        let IndentationSizes {
+            mut first_line_indent,
+            first_line_of_element_indent,
+            enumeration_padding,
+            other_line_indent,
+        } = self.indentation_sizes();
+
+        let mut first = true;
+        for (idx, element) in self.elements.iter().enumerate() {
+            let mut lines = element.lines();
+            if let Some(line) = lines.next() {
+                if first {
+                    first = false;
+                } else {
+                    writeln!(f)?;
+                }
+                match self.list_style {
+                    ListStyle::NoList => {
+                        write!(f, "{:first_line_indent$}{line}", "")?;
+                    }
+                    ListStyle::Bullet => {
+                        write!(f, "{:first_line_indent$}* {line}", "")?;
+                    }
+                    ListStyle::Enumerate => {
+                        write!(
+                            f,
+                            "{:first_line_indent$}{:>enumeration_padding$}. {line}",
+                            "", idx,
+                        )?;
+                    }
+                }
+            }
+            for line in lines {
+                writeln!(f)?;
+                write!(f, "{:other_line_indent$}{line}", "")?;
+            }
+            first_line_indent = first_line_of_element_indent;
+        }
+        Ok(())
+    }
+}
+
+impl FromIterator<String> for Description {
+    fn from_iter<T>(iter: T) -> Self
+    where
+        T: IntoIterator<Item = String>,
+    {
+        Self {
+            elements: iter.into_iter().collect(),
+            indent_mode: IndentMode::NoIndent,
+            list_style: ListStyle::NoList,
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::Description;
+    use crate::prelude::*;
+    use indoc::indoc;
+
+    #[test]
+    fn description_single_element() -> Result<()> {
+        let description = ["A B C".to_string()].into_iter().collect::<Description>();
+        verify_that!(description, displays_as(eq("A B C")))
+    }
+
+    #[test]
+    fn description_two_elements() -> Result<()> {
+        let description =
+            ["A B C".to_string(), "D E F".to_string()].into_iter().collect::<Description>();
+        verify_that!(description, displays_as(eq("A B C\nD E F")))
+    }
+
+    #[test]
+    fn description_indent_single_element() -> Result<()> {
+        let description = ["A B C".to_string()].into_iter().collect::<Description>().indent();
+        verify_that!(description, displays_as(eq("  A B C")))
+    }
+
+    #[test]
+    fn description_indent_two_elements() -> Result<()> {
+        let description = ["A B C".to_string(), "D E F".to_string()]
+            .into_iter()
+            .collect::<Description>()
+            .indent();
+        verify_that!(description, displays_as(eq("  A B C\n  D E F")))
+    }
+
+    #[test]
+    fn description_indent_two_elements_except_first_line() -> Result<()> {
+        let description = ["A B C".to_string(), "D E F".to_string()]
+            .into_iter()
+            .collect::<Description>()
+            .indent_except_first_line();
+        verify_that!(description, displays_as(eq("A B C\n  D E F")))
+    }
+
+    #[test]
+    fn description_indent_single_element_two_lines() -> Result<()> {
+        let description =
+            ["A B C\nD E F".to_string()].into_iter().collect::<Description>().indent();
+        verify_that!(description, displays_as(eq("  A B C\n  D E F")))
+    }
+
+    #[test]
+    fn description_indent_single_element_two_lines_except_first_line() -> Result<()> {
+        let description = ["A B C\nD E F".to_string()]
+            .into_iter()
+            .collect::<Description>()
+            .indent_except_first_line();
+        verify_that!(description, displays_as(eq("A B C\n  D E F")))
+    }
+
+    #[test]
+    fn description_bullet_single_element() -> Result<()> {
+        let description = ["A B C".to_string()].into_iter().collect::<Description>().bullet_list();
+        verify_that!(description, displays_as(eq("* A B C")))
+    }
+
+    #[test]
+    fn description_bullet_two_elements() -> Result<()> {
+        let description = ["A B C".to_string(), "D E F".to_string()]
+            .into_iter()
+            .collect::<Description>()
+            .bullet_list();
+        verify_that!(description, displays_as(eq("* A B C\n* D E F")))
+    }
+
+    #[test]
+    fn description_bullet_single_element_two_lines() -> Result<()> {
+        let description =
+            ["A B C\nD E F".to_string()].into_iter().collect::<Description>().bullet_list();
+        verify_that!(description, displays_as(eq("* A B C\n  D E F")))
+    }
+
+    #[test]
+    fn description_bullet_single_element_two_lines_indent_except_first_line() -> Result<()> {
+        let description = ["A B C\nD E F".to_string()]
+            .into_iter()
+            .collect::<Description>()
+            .bullet_list()
+            .indent_except_first_line();
+        verify_that!(description, displays_as(eq("* A B C\n    D E F")))
+    }
+
+    #[test]
+    fn description_bullet_two_elements_indent_except_first_line() -> Result<()> {
+        let description = ["A B C".to_string(), "D E F".to_string()]
+            .into_iter()
+            .collect::<Description>()
+            .bullet_list()
+            .indent_except_first_line();
+        verify_that!(description, displays_as(eq("* A B C\n  * D E F")))
+    }
+
+    #[test]
+    fn description_enumerate_single_element() -> Result<()> {
+        let description = ["A B C".to_string()].into_iter().collect::<Description>().enumerate();
+        verify_that!(description, displays_as(eq("0. A B C")))
+    }
+
+    #[test]
+    fn description_enumerate_two_elements() -> Result<()> {
+        let description = ["A B C".to_string(), "D E F".to_string()]
+            .into_iter()
+            .collect::<Description>()
+            .enumerate();
+        verify_that!(description, displays_as(eq("0. A B C\n1. D E F")))
+    }
+
+    #[test]
+    fn description_enumerate_single_element_two_lines() -> Result<()> {
+        let description =
+            ["A B C\nD E F".to_string()].into_iter().collect::<Description>().enumerate();
+        verify_that!(description, displays_as(eq("0. A B C\n   D E F")))
+    }
+
+    #[test]
+    fn description_enumerate_correct_indentation_with_large_index() -> Result<()> {
+        let description = ["A B C\nD E F"; 11]
+            .into_iter()
+            .map(str::to_string)
+            .collect::<Description>()
+            .enumerate();
+        verify_that!(
+            description,
+            displays_as(eq(indoc!(
+                "
+                 0. A B C
+                    D E F
+                 1. A B C
+                    D E F
+                 2. A B C
+                    D E F
+                 3. A B C
+                    D E F
+                 4. A B C
+                    D E F
+                 5. A B C
+                    D E F
+                 6. A B C
+                    D E F
+                 7. A B C
+                    D E F
+                 8. A B C
+                    D E F
+                 9. A B C
+                    D E F
+                10. A B C
+                    D E F"
+            )))
+        )
+    }
+}
diff --git a/src/matcher_support/edit_distance.rs b/src/matcher_support/edit_distance.rs
new file mode 100644
index 0000000..8469f42
--- /dev/null
+++ b/src/matcher_support/edit_distance.rs
@@ -0,0 +1,648 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use std::fmt::Debug;
+
+/// Maximum number of edits which can exist before [`edit_list`] falls back to a
+/// complete rewrite to produce the edit list.
+///
+/// Increasing this limit increases the accuracy of [`edit_list`] while
+/// quadratically increasing its worst-case runtime.
+const MAX_DISTANCE: i32 = 25;
+
+/// The difference between two inputs as produced by [`edit_list`].
+#[derive(Debug)]
+pub(crate) enum Difference<T> {
+    /// No differences were detected at all.
+    Equal,
+
+    /// At most [`MAX_DISTANCE`] edits are required to convert one input to the
+    /// other.
+    ///
+    /// Contains the list of [`Edit`] to perform the transformation.
+    Editable(Vec<Edit<T>>),
+
+    /// More than [`MAX_DISTANCE`] edits are required to convert one input to
+    /// the other.
+    ///
+    /// The inputs are therefore considered unrelated and no edit list is
+    /// provided.
+    Unrelated,
+}
+
+/// An edit operation on two sequences of `T`.
+#[derive(Debug, Clone)]
+pub(crate) enum Edit<T> {
+    /// An extra `T` was added to the actual sequence.
+    ExtraActual(T),
+
+    /// An extra `T` was added to the expected sequence.
+    ExtraExpected(T),
+
+    /// An element was added to each sequence.
+    Both(T),
+
+    /// Additional (unlisted) elements are present in the actual sequence.
+    ///
+    /// This is only output in the mode [`Mode::Prefix`]. Its presence precludes
+    /// reconstructing the actual sequence from the expected sequence.
+    AdditionalActual,
+}
+
+/// Controls the termination condition of [`edit_list`].
+#[derive(Clone, Copy)]
+pub(crate) enum Mode {
+    /// Indicates that the two arguments are intended to be equal.
+    ///
+    /// The entire edit list to transform between `actual` and `expected` is
+    /// returned.
+    Exact,
+
+    /// Indicates that `expected` is inteded to be a prefix of `actual`.
+    ///
+    /// Any additional parts of `actual` after the prefix `expected` are omitted
+    /// from the output.
+    Prefix,
+
+    /// Similar to [`Mode::Prefix`], except it is also assumed that `actual` has
+    /// some number of initial lines which should not be in the output.
+    ///
+    /// Any initial [`Edit::ExtraActual`] entries are replaced with
+    /// [`Edit::AdditionalActual`] in the edit list. If the first entry which is
+    /// not an [`Edit::ExtraActual`] is [`Edit::ExtraExpected`], then the last
+    /// [`Edit::ExtraActual`] is actual in the output.
+    Contains,
+}
+
+/// Computes the edit list of `actual` and `expected`.
+///
+/// If `actual` and `expected` are equal, then this returns
+/// [`Difference::Equal`]. If they are different but have an
+/// [edit distance](https://en.wikipedia.org/wiki/Edit_distance)
+/// of at most [`MAX_DISTANCE`], this returns [`Difference::Editable`] with the
+/// sequence of [`Edit`] which can be applied to `actual` to obtain `expected`.
+/// Otherwise this returns [`Difference::Unrelated`].
+///
+/// This uses [Myers Algorithm](https://neil.fraser.name/writing/diff/myers.pdf)
+/// with a maximum edit distance of [`MAX_DISTANCE`]. Thus the worst-case
+/// runtime is linear in both the input length and [`MAX_DISTANCE`].
+pub(crate) fn edit_list<T: PartialEq + Copy>(
+    actual: impl IntoIterator<Item = T>,
+    expected: impl IntoIterator<Item = T>,
+    mode: Mode,
+) -> Difference<T> {
+    let actual: Vec<_> = actual.into_iter().collect();
+    let expected: Vec<_> = expected.into_iter().collect();
+
+    let mut paths_last: Vec<Path<T>> = Vec::new();
+
+    for distance in 0..=MAX_DISTANCE {
+        let mut paths_current = Vec::new();
+        for k in (-distance..=distance).step_by(2) {
+            // The following will be None when k is at the edges of the range,
+            // since no paths have been created for k outside the range in the
+            // previous iteration.
+            let path_k_minus_1 = paths_last.get(index_of_k(k - 1, -distance + 1));
+            let path_k_plus_1 = paths_last.get(index_of_k(k + 1, -distance + 1));
+
+            let (mut path, edit) = match (path_k_minus_1, path_k_plus_1) {
+                // This the first (outer) iteration.
+                (None, None) => (Path::default(), None),
+
+                // k = -distance. There is no previous parent path yet.
+                (None, Some(path_k_plus_1)) => (
+                    path_k_plus_1.clone(),
+                    expected.get(path_k_plus_1.expected_endpoint).copied().map(Edit::ExtraExpected),
+                ),
+
+                // k = distance. There is no next parent path yet.
+                (Some(path_k_minus_1), None) => (
+                    path_k_minus_1.extend_actual_endpoint(),
+                    actual.get(path_k_minus_1.actual_endpoint).copied().map(Edit::ExtraActual),
+                ),
+
+                // k is strictly between -distance and distance. Both parent paths were set in the
+                // last iteration.
+                (Some(path_k_minus_1), Some(path_k_plus_1)) => {
+                    // This decides whether the algorithm prefers to add an edit
+                    // from the actual or from the expected when the rows differ. We
+                    // alternate so that the elements of differing blocks
+                    // interleave rather than all elements of each respective
+                    // side being output in a single block.
+                    if (distance % 2 == 0
+                        && path_k_plus_1.actual_endpoint > path_k_minus_1.actual_endpoint)
+                        || (distance % 2 == 1
+                            && path_k_plus_1.expected_endpoint > path_k_minus_1.expected_endpoint)
+                    {
+                        (
+                            path_k_plus_1.clone(),
+                            expected
+                                .get(path_k_plus_1.expected_endpoint)
+                                .copied()
+                                .map(Edit::ExtraExpected),
+                        )
+                    } else {
+                        (
+                            path_k_minus_1.extend_actual_endpoint(),
+                            actual
+                                .get(path_k_minus_1.actual_endpoint)
+                                .copied()
+                                .map(Edit::ExtraActual),
+                        )
+                    }
+                }
+            };
+            path.edits.extend(edit);
+
+            // Advance through any common elements starting at the current path.
+            let (mut actual_endpoint, mut expected_endpoint) =
+                (path.actual_endpoint, (path.actual_endpoint as i32 - k) as usize);
+            while actual_endpoint < actual.len()
+                && expected_endpoint < expected.len()
+                && actual[actual_endpoint] == expected[expected_endpoint]
+            {
+                path.edits.push(Edit::Both(actual[actual_endpoint]));
+                (actual_endpoint, expected_endpoint) = (actual_endpoint + 1, expected_endpoint + 1);
+            }
+
+            // If we have exhausted both inputs, we are done.
+            if actual_endpoint == actual.len() && expected_endpoint == expected.len() {
+                return if path.edits.iter().any(|v| !matches!(v, Edit::Both(_))) {
+                    if matches!(mode, Mode::Contains) {
+                        compress_prefix_and_suffix(&mut path.edits);
+                    }
+                    Difference::Editable(path.edits)
+                } else {
+                    Difference::Equal
+                };
+            }
+
+            path.actual_endpoint = actual_endpoint;
+            path.expected_endpoint = expected_endpoint;
+            paths_current.push(path);
+        }
+
+        if matches!(mode, Mode::Prefix) {
+            if let Some(path) = paths_current
+                .iter_mut()
+                .filter(|p| p.expected_endpoint == expected.len())
+                .max_by(|p1, p2| p1.edits.len().cmp(&p2.edits.len()))
+            {
+                // We've reached the end of the expected side but there could still be a
+                // corresponding line on the actual which we haven't picked up into the edit
+                // list. We'll just add it manually to the edit list. There's no
+                // real harm doing so -- worst case is that there's an
+                // additional line when there didn't have to be.
+                if let Some(Edit::ExtraExpected(_)) = path.edits.last() {
+                    if path.actual_endpoint < actual.len() {
+                        // The edits from the actual should come before the corresponding one from
+                        // the expected, so we insert rather than push.
+                        path.edits.insert(
+                            path.edits.len() - 1,
+                            Edit::ExtraActual(actual[path.actual_endpoint]),
+                        );
+                    }
+                }
+                path.edits.push(Edit::AdditionalActual);
+                return if path.edits.iter().any(|v| !matches!(v, Edit::Both(_))) {
+                    Difference::Editable(std::mem::take(&mut path.edits))
+                } else {
+                    Difference::Equal
+                };
+            }
+        }
+
+        paths_last = paths_current;
+    }
+
+    Difference::Unrelated
+}
+
+fn index_of_k(k: i32, k_min: i32) -> usize {
+    ((k - k_min) / 2) as usize
+}
+
+fn compress_prefix_and_suffix<T>(edits: &mut Vec<Edit<T>>) {
+    if let Some(mut first_non_extra_actual_edit) =
+        edits.iter().position(|e| !matches!(e, Edit::ExtraActual(_)))
+    {
+        if first_non_extra_actual_edit > 1
+            && matches!(edits[first_non_extra_actual_edit], Edit::ExtraExpected(_))
+        {
+            first_non_extra_actual_edit -= 1;
+        }
+        edits.splice(..first_non_extra_actual_edit, [Edit::AdditionalActual]);
+    }
+
+    if let Some(mut last_non_extra_actual_edit) =
+        edits.iter().rposition(|e| !matches!(e, Edit::ExtraActual(_)))
+    {
+        if last_non_extra_actual_edit < edits.len() - 1
+            && matches!(edits[last_non_extra_actual_edit], Edit::ExtraExpected(_))
+        {
+            last_non_extra_actual_edit += 1;
+        }
+        edits.splice(last_non_extra_actual_edit + 1.., [Edit::AdditionalActual]);
+    }
+}
+
+#[derive(Clone)]
+struct Path<T: Clone> {
+    actual_endpoint: usize,
+    expected_endpoint: usize,
+    edits: Vec<Edit<T>>,
+}
+
+impl<T: Clone> Default for Path<T> {
+    fn default() -> Self {
+        Self { actual_endpoint: 0, expected_endpoint: 0, edits: vec![] }
+    }
+}
+
+impl<T: Clone> Path<T> {
+    fn extend_actual_endpoint(&self) -> Self {
+        Self {
+            actual_endpoint: self.actual_endpoint + 1,
+            expected_endpoint: self.expected_endpoint,
+            edits: self.edits.clone(),
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::prelude::*;
+    use quickcheck::{quickcheck, Arbitrary, TestResult};
+
+    #[test]
+    fn returns_equal_when_strings_are_equal() -> Result<()> {
+        let result = edit_list(["A string"], ["A string"], Mode::Exact);
+        verify_that!(result, matches_pattern!(Difference::Equal))
+    }
+
+    #[test]
+    fn returns_sequence_of_two_common_parts() -> Result<()> {
+        let result = edit_list(
+            ["A string (1)", "A string (2)"],
+            ["A string (1)", "A string (2)"],
+            Mode::Exact,
+        );
+        verify_that!(result, matches_pattern!(Difference::Equal))
+    }
+
+    #[test]
+    fn returns_extra_actual_when_only_actual_has_content() -> Result<()> {
+        let result = edit_list(["A string"], [], Mode::Exact);
+        verify_that!(
+            result,
+            matches_pattern!(Difference::Editable(elements_are![matches_pattern!(
+                Edit::ExtraActual(eq("A string"))
+            )]))
+        )
+    }
+
+    #[test]
+    fn returns_extra_expected_when_only_expected_has_content() -> Result<()> {
+        let result = edit_list([], ["A string"], Mode::Exact);
+        verify_that!(
+            result,
+            matches_pattern!(Difference::Editable(elements_are![matches_pattern!(
+                Edit::ExtraExpected(eq("A string"))
+            )]))
+        )
+    }
+
+    #[test]
+    fn returns_extra_actual_followed_by_extra_expected_with_two_unequal_strings() -> Result<()> {
+        let result = edit_list(["A string"], ["Another string"], Mode::Exact);
+        verify_that!(
+            result,
+            matches_pattern!(Difference::Editable(elements_are![
+                matches_pattern!(Edit::ExtraActual(eq("A string"))),
+                matches_pattern!(Edit::ExtraExpected(eq("Another string"))),
+            ]))
+        )
+    }
+
+    #[test]
+    fn interleaves_extra_actual_and_extra_expected_when_multiple_lines_differ() -> Result<()> {
+        let result =
+            edit_list(["A string", "A string"], ["Another string", "Another string"], Mode::Exact);
+        verify_that!(
+            result,
+            matches_pattern!(Difference::Editable(elements_are![
+                matches_pattern!(Edit::ExtraActual(eq("A string"))),
+                matches_pattern!(Edit::ExtraExpected(eq("Another string"))),
+                matches_pattern!(Edit::ExtraActual(eq("A string"))),
+                matches_pattern!(Edit::ExtraExpected(eq("Another string"))),
+            ]))
+        )
+    }
+
+    #[test]
+    fn returns_common_part_plus_difference_when_there_is_common_prefix() -> Result<()> {
+        let result = edit_list(
+            ["Common part", "Actual only"],
+            ["Common part", "Expected only"],
+            Mode::Exact,
+        );
+        verify_that!(
+            result,
+            matches_pattern!(Difference::Editable(elements_are![
+                matches_pattern!(Edit::Both(eq("Common part"))),
+                matches_pattern!(Edit::ExtraActual(eq("Actual only"))),
+                matches_pattern!(Edit::ExtraExpected(eq("Expected only"))),
+            ]))
+        )
+    }
+
+    #[test]
+    fn returns_common_part_plus_extra_actual_when_actual_has_extra_suffix() -> Result<()> {
+        let result = edit_list(["Common part", "Actual only"], ["Common part"], Mode::Exact);
+        verify_that!(
+            result,
+            matches_pattern!(Difference::Editable(elements_are![
+                matches_pattern!(Edit::Both(eq("Common part"))),
+                matches_pattern!(Edit::ExtraActual(eq("Actual only"))),
+            ]))
+        )
+    }
+
+    #[test]
+    fn returns_common_part_plus_extra_expected_when_expected_has_extra_suffix() -> Result<()> {
+        let result = edit_list(["Common part"], ["Common part", "Expected only"], Mode::Exact);
+        verify_that!(
+            result,
+            matches_pattern!(Difference::Editable(elements_are![
+                matches_pattern!(Edit::Both(eq("Common part"))),
+                matches_pattern!(Edit::ExtraExpected(eq("Expected only"))),
+            ]))
+        )
+    }
+
+    #[test]
+    fn returns_difference_plus_common_part_when_there_is_common_suffix() -> Result<()> {
+        let result = edit_list(
+            ["Actual only", "Common part"],
+            ["Expected only", "Common part"],
+            Mode::Exact,
+        );
+        verify_that!(
+            result,
+            matches_pattern!(Difference::Editable(elements_are![
+                matches_pattern!(Edit::ExtraActual(eq("Actual only"))),
+                matches_pattern!(Edit::ExtraExpected(eq("Expected only"))),
+                matches_pattern!(Edit::Both(eq("Common part"))),
+            ]))
+        )
+    }
+
+    #[test]
+    fn returns_difference_plus_common_part_plus_difference_when_there_is_common_infix() -> Result<()>
+    {
+        let result = edit_list(
+            ["Actual only (1)", "Common part", "Actual only (2)"],
+            ["Expected only (1)", "Common part", "Expected only (2)"],
+            Mode::Exact,
+        );
+        verify_that!(
+            result,
+            matches_pattern!(Difference::Editable(elements_are![
+                matches_pattern!(Edit::ExtraActual(eq("Actual only (1)"))),
+                matches_pattern!(Edit::ExtraExpected(eq("Expected only (1)"))),
+                matches_pattern!(Edit::Both(eq("Common part"))),
+                matches_pattern!(Edit::ExtraActual(eq("Actual only (2)"))),
+                matches_pattern!(Edit::ExtraExpected(eq("Expected only (2)"))),
+            ]))
+        )
+    }
+
+    #[test]
+    fn returns_common_part_plus_difference_plus_common_part_when_there_is_common_prefix_and_suffix()
+    -> Result<()> {
+        let result = edit_list(
+            ["Common part (1)", "Actual only", "Common part (2)"],
+            ["Common part (1)", "Expected only", "Common part (2)"],
+            Mode::Exact,
+        );
+        verify_that!(
+            result,
+            matches_pattern!(Difference::Editable(elements_are![
+                matches_pattern!(Edit::Both(eq("Common part (1)"))),
+                matches_pattern!(Edit::ExtraActual(eq("Actual only"))),
+                matches_pattern!(Edit::ExtraExpected(eq("Expected only"))),
+                matches_pattern!(Edit::Both(eq("Common part (2)"))),
+            ]))
+        )
+    }
+
+    #[test]
+    fn returns_common_part_plus_extra_actual_plus_common_part_when_there_is_common_prefix_and_suffix()
+    -> Result<()> {
+        let result = edit_list(
+            ["Common part (1)", "Actual only", "Common part (2)"],
+            ["Common part (1)", "Common part (2)"],
+            Mode::Exact,
+        );
+        verify_that!(
+            result,
+            matches_pattern!(Difference::Editable(elements_are![
+                matches_pattern!(Edit::Both(eq("Common part (1)"))),
+                matches_pattern!(Edit::ExtraActual(eq("Actual only"))),
+                matches_pattern!(Edit::Both(eq("Common part (2)"))),
+            ]))
+        )
+    }
+
+    #[test]
+    fn returns_common_part_plus_extra_expected_plus_common_part_when_there_is_common_prefix_and_suffix()
+    -> Result<()> {
+        let result = edit_list(
+            ["Common part (1)", "Common part (2)"],
+            ["Common part (1)", "Expected only", "Common part (2)"],
+            Mode::Exact,
+        );
+        verify_that!(
+            result,
+            matches_pattern!(Difference::Editable(elements_are![
+                matches_pattern!(Edit::Both(eq("Common part (1)"))),
+                matches_pattern!(Edit::ExtraExpected(eq("Expected only"))),
+                matches_pattern!(Edit::Both(eq("Common part (2)"))),
+            ]))
+        )
+    }
+
+    #[test]
+    fn skips_extra_parts_on_actual_at_end_in_prefix_mode() -> Result<()> {
+        let result = edit_list(
+            ["Common part", "Actual only"],
+            ["Expected only", "Common part"],
+            Mode::Prefix,
+        );
+        verify_that!(
+            result,
+            matches_pattern!(Difference::Editable(not(contains(matches_pattern!(
+                Edit::ExtraActual(eq("Actual only"))
+            )))))
+        )
+    }
+
+    #[test]
+    fn does_not_skip_extra_parts_on_actual_in_prefix_mode_at_end_when_they_are_in_common()
+    -> Result<()> {
+        let result = edit_list(
+            ["Actual only", "Common part"],
+            ["Expected only", "Common part"],
+            Mode::Prefix,
+        );
+        verify_that!(
+            result,
+            matches_pattern!(Difference::Editable(elements_are![
+                matches_pattern!(Edit::ExtraActual(eq("Actual only"))),
+                matches_pattern!(Edit::ExtraExpected(eq("Expected only"))),
+                matches_pattern!(Edit::Both(eq("Common part"))),
+            ]))
+        )
+    }
+
+    #[test]
+    fn does_not_skip_corresponding_line_on_actual_when_actual_and_expected_differ_in_prefix_mode()
+    -> Result<()> {
+        let result = edit_list(["Actual only"], ["Expected only"], Mode::Prefix);
+        verify_that!(
+            result,
+            matches_pattern!(Difference::Editable(elements_are![
+                matches_pattern!(Edit::ExtraActual(eq("Actual only"))),
+                matches_pattern!(Edit::ExtraExpected(eq("Expected only"))),
+                matches_pattern!(Edit::AdditionalActual),
+            ]))
+        )
+    }
+
+    #[test]
+    fn returns_unrelated_when_maximum_distance_exceeded() -> Result<()> {
+        let result = edit_list(0..=20, 20..40, Mode::Exact);
+        verify_that!(result, matches_pattern!(Difference::Unrelated))
+    }
+
+    quickcheck! {
+        fn edit_list_edits_actual_to_expected(
+            actual: Vec<Alphabet>,
+            expected: Vec<Alphabet>
+        ) -> TestResult {
+            match edit_list(actual.clone(), expected.clone(), Mode::Exact) {
+                Difference::Equal => TestResult::from_bool(actual == expected),
+                Difference::Editable(edit_list) => {
+                    TestResult::from_bool(apply_edits_to_actual(&edit_list, &actual) == expected)
+                }
+                Difference::Unrelated => {
+                    if actual == expected {
+                        TestResult::failed()
+                    } else {
+                        TestResult::discard()
+                    }
+                }
+            }
+        }
+    }
+
+    quickcheck! {
+        fn edit_list_edits_expected_to_actual(
+            actual: Vec<Alphabet>,
+            expected: Vec<Alphabet>
+        ) -> TestResult {
+            match edit_list(actual.clone(), expected.clone(), Mode::Exact) {
+                Difference::Equal => TestResult::from_bool(actual == expected),
+                Difference::Editable(edit_list) => {
+                    TestResult::from_bool(apply_edits_to_expected(&edit_list, &expected) == actual)
+                }
+                Difference::Unrelated => {
+                    if actual == expected {
+                        TestResult::failed()
+                    } else {
+                        TestResult::discard()
+                    }
+                }
+            }
+        }
+    }
+
+    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
+    enum Alphabet {
+        A,
+        B,
+        C,
+    }
+
+    impl Arbitrary for Alphabet {
+        fn arbitrary(g: &mut quickcheck::Gen) -> Self {
+            g.choose(&[Alphabet::A, Alphabet::B, Alphabet::C]).copied().unwrap()
+        }
+    }
+
+    fn apply_edits_to_actual<T: PartialEq + Debug + Copy>(
+        edit_list: &[Edit<T>],
+        actual: &[T],
+    ) -> Vec<T> {
+        let mut result = Vec::new();
+        let mut actual_iter = actual.iter();
+        for edit in edit_list {
+            match edit {
+                Edit::ExtraActual(value) => {
+                    assert_that!(actual_iter.next(), some(eq(value)));
+                }
+                Edit::ExtraExpected(value) => {
+                    result.push(*value);
+                }
+                Edit::Both(value) => {
+                    assert_that!(actual_iter.next(), some(eq(value)));
+                    result.push(*value);
+                }
+                Edit::AdditionalActual => {
+                    fail!("Unexpected Edit::AdditionalActual").unwrap();
+                }
+            }
+        }
+        assert_that!(actual_iter.next(), none());
+        result
+    }
+
+    fn apply_edits_to_expected<T: PartialEq + Debug + Copy>(
+        edit_list: &[Edit<T>],
+        expected: &[T],
+    ) -> Vec<T> {
+        let mut result = Vec::new();
+        let mut expected_iter = expected.iter();
+        for edit in edit_list {
+            match edit {
+                Edit::ExtraActual(value) => {
+                    result.push(*value);
+                }
+                Edit::ExtraExpected(value) => {
+                    assert_that!(expected_iter.next(), some(eq(value)));
+                }
+                Edit::Both(value) => {
+                    assert_that!(expected_iter.next(), some(eq(value)));
+                    result.push(*value);
+                }
+                Edit::AdditionalActual => {
+                    fail!("Unexpected Edit::AdditionalActual").unwrap();
+                }
+            }
+        }
+        assert_that!(expected_iter.next(), none());
+        result
+    }
+}
diff --git a/src/matcher_support/mod.rs b/src/matcher_support/mod.rs
new file mode 100644
index 0000000..8c30161
--- /dev/null
+++ b/src/matcher_support/mod.rs
@@ -0,0 +1,25 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Utilities to facilitate writing matchers.
+//!
+//! Tests normally do not need to import anything from this module. Some of
+//! these facilities could be useful to downstream users writing custom
+//! matchers.
+
+pub(crate) mod count_elements;
+pub mod description;
+pub(crate) mod edit_distance;
+pub(crate) mod summarize_diff;
+pub(crate) mod zipped_iterator;
diff --git a/src/matcher_support/summarize_diff.rs b/src/matcher_support/summarize_diff.rs
new file mode 100644
index 0000000..a045282
--- /dev/null
+++ b/src/matcher_support/summarize_diff.rs
@@ -0,0 +1,367 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#![doc(hidden)]
+
+use crate::matcher_support::edit_distance;
+#[rustversion::since(1.70)]
+use std::io::IsTerminal;
+use std::{
+    borrow::Cow,
+    fmt::{Display, Write},
+};
+
+/// Returns a string describing how the expected and actual lines differ.
+///
+/// This is included in a match explanation for [`EqMatcher`] and
+/// [`crate::matchers::str_matcher::StrMatcher`].
+///
+/// If the actual value has less than two lines, or the two differ by more than
+/// the maximum edit distance, then this returns the empty string. If the two
+/// are equal, it returns a simple statement that they are equal. Otherwise,
+/// this constructs a unified diff view of the actual and expected values.
+pub(crate) fn create_diff(
+    actual_debug: &str,
+    expected_debug: &str,
+    diff_mode: edit_distance::Mode,
+) -> Cow<'static, str> {
+    if actual_debug.lines().count() < 2 {
+        // If the actual debug is only one line, then there is no point in doing a
+        // line-by-line diff.
+        return "".into();
+    }
+    match edit_distance::edit_list(actual_debug.lines(), expected_debug.lines(), diff_mode) {
+        edit_distance::Difference::Equal => "No difference found between debug strings.".into(),
+        edit_distance::Difference::Editable(edit_list) => format!(
+            "\nDifference({} / {}):{}",
+            LineStyle::extra_actual_style().style("actual"),
+            LineStyle::extra_expected_style().style("expected"),
+            edit_list.into_iter().collect::<BufferedSummary>(),
+        )
+        .into(),
+        edit_distance::Difference::Unrelated => "".into(),
+    }
+}
+
+/// Returns a string describing how the expected and actual differ after
+/// reversing the lines in each.
+///
+/// This is similar to [`create_diff`] except that it first reverses the lines
+/// in both the expected and actual values, then reverses the constructed edit
+/// list. When `diff_mode` is [`edit_distance::Mode::Prefix`], this becomes a
+/// diff of the suffix for use by [`ends_with`][crate::matchers::ends_with].
+pub(crate) fn create_diff_reversed(
+    actual_debug: &str,
+    expected_debug: &str,
+    diff_mode: edit_distance::Mode,
+) -> Cow<'static, str> {
+    if actual_debug.lines().count() < 2 {
+        // If the actual debug is only one line, then there is no point in doing a
+        // line-by-line diff.
+        return "".into();
+    }
+    let mut actual_lines_reversed = actual_debug.lines().collect::<Vec<_>>();
+    let mut expected_lines_reversed = expected_debug.lines().collect::<Vec<_>>();
+    actual_lines_reversed.reverse();
+    expected_lines_reversed.reverse();
+    match edit_distance::edit_list(actual_lines_reversed, expected_lines_reversed, diff_mode) {
+        edit_distance::Difference::Equal => "No difference found between debug strings.".into(),
+        edit_distance::Difference::Editable(mut edit_list) => {
+            edit_list.reverse();
+            format!(
+                "\nDifference({} / {}):{}",
+                LineStyle::extra_actual_style().style("actual"),
+                LineStyle::extra_expected_style().style("expected"),
+                edit_list.into_iter().collect::<BufferedSummary>(),
+            )
+            .into()
+        }
+        edit_distance::Difference::Unrelated => "".into(),
+    }
+}
+
+// Aggregator collecting the lines to be printed in the difference summary.
+//
+// This is buffered in order to allow a future line to potentially impact how
+// the current line would be printed.
+struct BufferedSummary<'a> {
+    summary: String,
+    buffer: Buffer<'a>,
+}
+
+impl<'a> BufferedSummary<'a> {
+    // Appends a new line which is common to both actual and expected.
+    fn feed_common_lines(&mut self, common_line: &'a str) {
+        let Buffer::CommonLineBuffer(ref mut common_lines) = self.buffer;
+        common_lines.push(common_line);
+    }
+
+    // Appends a new line which is found only in the actual string.
+    fn feed_extra_actual(&mut self, extra_actual: &'a str) {
+        self.buffer.flush(&mut self.summary).unwrap();
+        write!(&mut self.summary, "\n{}", LineStyle::extra_actual_style().style(extra_actual))
+            .unwrap();
+    }
+
+    // Appends a new line which is found only in the expected string.
+    fn feed_extra_expected(&mut self, extra_expected: &str) {
+        self.flush_buffer();
+        write!(&mut self.summary, "\n{}", LineStyle::extra_expected_style().style(extra_expected))
+            .unwrap();
+    }
+
+    // Appends a comment for the additional line at the start or the end of the
+    // actual string which should be omitted.
+    fn feed_additional_actual(&mut self) {
+        self.flush_buffer();
+        write!(
+            &mut self.summary,
+            "\n{}",
+            LineStyle::comment_style().style("<---- remaining lines omitted ---->")
+        )
+        .unwrap();
+    }
+
+    fn flush_buffer(&mut self) {
+        self.buffer.flush(&mut self.summary).unwrap();
+    }
+}
+
+impl<'a> FromIterator<edit_distance::Edit<&'a str>> for BufferedSummary<'a> {
+    fn from_iter<T: IntoIterator<Item = edit_distance::Edit<&'a str>>>(iter: T) -> Self {
+        let mut buffered_summary =
+            BufferedSummary { summary: String::new(), buffer: Buffer::CommonLineBuffer(vec![]) };
+        for edit in iter {
+            match edit {
+                edit_distance::Edit::Both(same) => {
+                    buffered_summary.feed_common_lines(same);
+                }
+                edit_distance::Edit::ExtraActual(actual) => {
+                    buffered_summary.feed_extra_actual(actual);
+                }
+                edit_distance::Edit::ExtraExpected(expected) => {
+                    buffered_summary.feed_extra_expected(expected);
+                }
+                edit_distance::Edit::AdditionalActual => {
+                    buffered_summary.feed_additional_actual();
+                }
+            };
+        }
+        buffered_summary.flush_buffer();
+
+        buffered_summary
+    }
+}
+
+impl<'a> Display for BufferedSummary<'a> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        if !matches!(self.buffer, Buffer::CommonLineBuffer(ref b) if b.is_empty()) {
+            panic!("Buffer is not empty. This is a bug in gtest_rust.")
+        }
+        self.summary.fmt(f)
+    }
+}
+
+// This needs to be an enum as there will be in a follow-up PR new types of
+// buffer, most likely actual and expected lines, to be compared with expected
+// and actual lines for line to line comparison.
+enum Buffer<'a> {
+    CommonLineBuffer(Vec<&'a str>),
+}
+
+impl<'a> Buffer<'a> {
+    fn flush(&mut self, writer: impl std::fmt::Write) -> std::fmt::Result {
+        match self {
+            Buffer::CommonLineBuffer(common_lines) => {
+                Self::flush_common_lines(std::mem::take(common_lines), writer)?
+            }
+        };
+        Ok(())
+    }
+
+    fn flush_common_lines(
+        common_lines: Vec<&'a str>,
+        mut writer: impl std::fmt::Write,
+    ) -> std::fmt::Result {
+        // The number of the lines kept before and after the compressed lines.
+        const COMMON_LINES_CONTEXT_SIZE: usize = 2;
+
+        if common_lines.len() <= 2 * COMMON_LINES_CONTEXT_SIZE + 1 {
+            for line in common_lines {
+                write!(writer, "\n{}", LineStyle::unchanged_style().style(line))?;
+            }
+            return Ok(());
+        }
+
+        let start_context = &common_lines[0..COMMON_LINES_CONTEXT_SIZE];
+
+        for line in start_context {
+            write!(writer, "\n{}", LineStyle::unchanged_style().style(line))?;
+        }
+
+        write!(
+            writer,
+            "\n{}",
+            LineStyle::comment_style().style(&format!(
+                "<---- {} common lines omitted ---->",
+                common_lines.len() - 2 * COMMON_LINES_CONTEXT_SIZE
+            )),
+        )?;
+
+        let end_context =
+            &common_lines[common_lines.len() - COMMON_LINES_CONTEXT_SIZE..common_lines.len()];
+
+        for line in end_context {
+            write!(writer, "\n{}", LineStyle::unchanged_style().style(line))?;
+        }
+        Ok(())
+    }
+}
+
+// Use ANSI code to enable styling on the summary lines.
+//
+// See https://en.wikipedia.org/wiki/ANSI_escape_code.
+struct LineStyle {
+    ansi_prefix: &'static str,
+    ansi_suffix: &'static str,
+    header: char,
+}
+
+impl LineStyle {
+    // Font in red and bold
+    fn extra_actual_style() -> Self {
+        Self { ansi_prefix: "\x1B[1;31m", ansi_suffix: "\x1B[0m", header: '-' }
+    }
+
+    // Font in green and bold
+    fn extra_expected_style() -> Self {
+        Self { ansi_prefix: "\x1B[1;32m", ansi_suffix: "\x1B[0m", header: '+' }
+    }
+
+    // Font in italic
+    fn comment_style() -> Self {
+        Self { ansi_prefix: "\x1B[3m", ansi_suffix: "\x1B[0m", header: ' ' }
+    }
+
+    // No ansi styling
+    fn unchanged_style() -> Self {
+        Self { ansi_prefix: "", ansi_suffix: "", header: ' ' }
+    }
+
+    fn style(self, line: &str) -> StyledLine<'_> {
+        StyledLine { style: self, line }
+    }
+}
+
+struct StyledLine<'a> {
+    style: LineStyle,
+    line: &'a str,
+}
+
+impl<'a> Display for StyledLine<'a> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        if stdout_supports_color() {
+            write!(
+                f,
+                "{}{}{}{}",
+                self.style.header, self.style.ansi_prefix, self.line, self.style.ansi_suffix
+            )
+        } else {
+            write!(f, "{}{}", self.style.header, self.line)
+        }
+    }
+}
+
+#[rustversion::since(1.70)]
+fn stdout_supports_color() -> bool {
+    match (is_env_var_set("NO_COLOR"), is_env_var_set("FORCE_COLOR")) {
+        (true, _) => false,
+        (false, true) => true,
+        (false, false) => std::io::stdout().is_terminal(),
+    }
+}
+
+#[rustversion::not(since(1.70))]
+fn stdout_supports_color() -> bool {
+    is_env_var_set("FORCE_COLOR")
+}
+
+fn is_env_var_set(var: &'static str) -> bool {
+    std::env::var(var).map(|s| !s.is_empty()).unwrap_or(false)
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::{matcher_support::edit_distance::Mode, prelude::*};
+    use indoc::indoc;
+
+    // Make a long text with each element of the iterator on one line.
+    // `collection` must contains at least one element.
+    fn build_text<T: Display>(mut collection: impl Iterator<Item = T>) -> String {
+        let mut text = String::new();
+        write!(&mut text, "{}", collection.next().expect("Provided collection without elements"))
+            .unwrap();
+        for item in collection {
+            write!(&mut text, "\n{}", item).unwrap();
+        }
+        text
+    }
+
+    #[test]
+    fn create_diff_smaller_than_one_line() -> Result<()> {
+        verify_that!(create_diff("One", "Two", Mode::Exact), eq(""))
+    }
+
+    #[test]
+    fn create_diff_exact_same() -> Result<()> {
+        let expected = indoc! {"
+            One
+            Two
+            "};
+        let actual = indoc! {"
+        One
+        Two
+        "};
+        verify_that!(
+            create_diff(expected, actual, Mode::Exact),
+            eq("No difference found between debug strings.")
+        )
+    }
+
+    #[test]
+    fn create_diff_exact_unrelated() -> Result<()> {
+        verify_that!(create_diff(&build_text(1..500), &build_text(501..1000), Mode::Exact), eq(""))
+    }
+
+    #[test]
+    fn create_diff_exact_small_difference_no_color() -> Result<()> {
+        std::env::set_var("NO_COLOR", "1");
+
+        verify_that!(
+            create_diff(&build_text(1..50), &build_text(1..51), Mode::Exact),
+            eq(indoc! {
+                "
+
+                Difference(-actual / +expected):
+                 1
+                 2
+                 <---- 45 common lines omitted ---->
+                 48
+                 49
+                +50"
+            })
+        )
+    }
+}
diff --git a/src/matcher_support/zipped_iterator.rs b/src/matcher_support/zipped_iterator.rs
new file mode 100644
index 0000000..d1cbaf3
--- /dev/null
+++ b/src/matcher_support/zipped_iterator.rs
@@ -0,0 +1,85 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/// Zips up two iterators into a single iterator of pairs.
+///
+/// This is identical to [`Iterator::zip`] except that this version allows the
+/// caller to determine whether the two iterators had mismatching sizes using
+/// the method [`ZippedIterator::has_size_mismatch`].
+///
+/// [`Iterator::zip`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.zip
+pub(crate) fn zip<I1, I2>(left: I1, right: I2) -> ZippedIterator<I1, I2> {
+    ZippedIterator { left, right, has_size_mismatch: false, consumed_elements: 0 }
+}
+
+/// An iterator over pairs of the elements of two constituent iterators, which
+/// keeps track of whether the two iterators have the same size.
+///
+/// This is identical to [`Zip`] except that it allows the caller to determine
+/// whether the two iterators had mismatching sizes using the method
+/// [`ZippedIterator::has_size_mismatch`].
+///
+/// [`Zip`]: https://doc.rust-lang.org/std/iter/struct.Zip.html
+pub(crate) struct ZippedIterator<I1, I2> {
+    left: I1,
+    right: I2,
+    has_size_mismatch: bool,
+    consumed_elements: usize,
+}
+
+impl<I1: Iterator, I2> ZippedIterator<I1, I2> {
+    /// Returns whether a mismatch in the two sizes of the two iterators was
+    /// detected during iteration.
+    ///
+    /// This returns `true` if and only if, at some previous call to
+    /// [`Iterator::next`] on this instance, one of the constituent iterators
+    /// had a next element and the other did not.
+    ///
+    /// [`Iterator::next`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next
+    pub(crate) fn has_size_mismatch(&self) -> bool {
+        self.has_size_mismatch
+    }
+
+    /// Returns the number of elements in the left iterator.
+    ///
+    /// This iterates through the remainder of the left iterator if necessary in
+    /// order to get the true number of elements. It therefore consumes `self`.
+    pub(crate) fn left_size(mut self) -> usize {
+        self.consumed_elements + self.left.by_ref().count()
+    }
+}
+
+impl<I1: Iterator, I2: Iterator> Iterator for ZippedIterator<I1, I2> {
+    type Item = (I1::Item, I2::Item);
+
+    fn next(&mut self) -> Option<(I1::Item, I2::Item)> {
+        match (self.left.next(), self.right.next()) {
+            (Some(v1), Some(v2)) => {
+                self.consumed_elements += 1;
+                Some((v1, v2))
+            }
+            (Some(_), None) => {
+                // Consumed elements counts only elements from self.left
+                self.consumed_elements += 1;
+                self.has_size_mismatch = true;
+                None
+            }
+            (None, Some(_)) => {
+                self.has_size_mismatch = true;
+                None
+            }
+            (None, None) => None,
+        }
+    }
+}
diff --git a/src/matchers/all_matcher.rs b/src/matchers/all_matcher.rs
new file mode 100644
index 0000000..cc959c7
--- /dev/null
+++ b/src/matchers/all_matcher.rs
@@ -0,0 +1,205 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// There are no visible documentation elements in this module; the declarative
+// macro is documented at the top level.
+#![doc(hidden)]
+
+/// Matches a value which all of the given matchers match.
+///
+/// Each argument is a [`Matcher`][crate::matcher::Matcher] which matches
+/// against the actual value.
+///
+/// For example:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!("A string", all!(starts_with("A"), ends_with("string")))?; // Passes
+/// #     Ok(())
+/// # }
+/// # fn should_fail() -> Result<()> {
+/// verify_that!("A string", all!(starts_with("A"), ends_with("not a string")))?; // Fails
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # should_fail().unwrap_err();
+/// ```
+///
+/// Using this macro is equivalent to using the
+/// [`and`][crate::matcher::Matcher::and] method:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(10, gt(9).and(lt(11)))?; // Also passes
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// Assertion failure messages are not guaranteed to be identical, however.
+#[macro_export]
+macro_rules! all {
+    ($($matcher:expr),* $(,)?) => {{
+        use $crate::matchers::all_matcher::internal::AllMatcher;
+        AllMatcher::new([$(Box::new($matcher)),*])
+    }}
+}
+
+/// Functionality needed by the [`all`] macro.
+///
+/// For internal use only. API stablility is not guaranteed!
+#[doc(hidden)]
+pub mod internal {
+    use crate::matcher::{Matcher, MatcherResult};
+    use crate::matcher_support::description::Description;
+    use crate::matchers::anything;
+    use std::fmt::Debug;
+
+    /// A matcher which matches an input value matched by all matchers in the
+    /// array `components`.
+    ///
+    /// For internal use only. API stablility is not guaranteed!
+    #[doc(hidden)]
+    pub struct AllMatcher<'a, T: Debug + ?Sized, const N: usize> {
+        components: [Box<dyn Matcher<ActualT = T> + 'a>; N],
+    }
+
+    impl<'a, T: Debug + ?Sized, const N: usize> AllMatcher<'a, T, N> {
+        /// Constructs an [`AllMatcher`] with the given component matchers.
+        ///
+        /// Intended for use only by the [`all`] macro.
+        pub fn new(components: [Box<dyn Matcher<ActualT = T> + 'a>; N]) -> Self {
+            Self { components }
+        }
+    }
+
+    impl<'a, T: Debug + ?Sized, const N: usize> Matcher for AllMatcher<'a, T, N> {
+        type ActualT = T;
+
+        fn matches(&self, actual: &Self::ActualT) -> MatcherResult {
+            for component in &self.components {
+                match component.matches(actual) {
+                    MatcherResult::NoMatch => {
+                        return MatcherResult::NoMatch;
+                    }
+                    MatcherResult::Match => {}
+                }
+            }
+            MatcherResult::Match
+        }
+
+        fn explain_match(&self, actual: &Self::ActualT) -> String {
+            match N {
+                0 => anything::<T>().explain_match(actual),
+                1 => self.components[0].explain_match(actual),
+                _ => {
+                    let failures = self
+                        .components
+                        .iter()
+                        .filter(|component| component.matches(actual).is_no_match())
+                        .map(|component| component.explain_match(actual))
+                        .collect::<Description>();
+                    if failures.len() == 1 {
+                        format!("{}", failures)
+                    } else {
+                        format!("{}", failures.bullet_list().indent_except_first_line())
+                    }
+                }
+            }
+        }
+
+        fn describe(&self, matcher_result: MatcherResult) -> String {
+            match N {
+                0 => anything::<T>().describe(matcher_result),
+                1 => self.components[0].describe(matcher_result),
+                _ => {
+                    let properties = self
+                        .components
+                        .iter()
+                        .map(|m| m.describe(matcher_result))
+                        .collect::<Description>()
+                        .bullet_list()
+                        .indent();
+                    format!(
+                        "{}:\n{properties}",
+                        if matcher_result.into() {
+                            "has all the following properties"
+                        } else {
+                            "has at least one of the following properties"
+                        }
+                    )
+                }
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::internal;
+    use crate::matcher::{Matcher, MatcherResult};
+    use crate::prelude::*;
+    use indoc::indoc;
+
+    #[test]
+    fn description_shows_more_than_one_matcher() -> Result<()> {
+        let first_matcher = starts_with("A");
+        let second_matcher = ends_with("string");
+        let matcher: internal::AllMatcher<String, 2> = all!(first_matcher, second_matcher);
+
+        verify_that!(
+            matcher.describe(MatcherResult::Match),
+            eq(indoc!(
+                "
+                has all the following properties:
+                  * starts with prefix \"A\"
+                  * ends with suffix \"string\""
+            ))
+        )
+    }
+
+    #[test]
+    fn description_shows_one_matcher_directly() -> Result<()> {
+        let first_matcher = starts_with("A");
+        let matcher: internal::AllMatcher<String, 1> = all!(first_matcher);
+
+        verify_that!(matcher.describe(MatcherResult::Match), eq("starts with prefix \"A\""))
+    }
+
+    #[test]
+    fn mismatch_description_shows_which_matcher_failed_if_more_than_one_constituent() -> Result<()>
+    {
+        let first_matcher = starts_with("Another");
+        let second_matcher = ends_with("string");
+        let matcher: internal::AllMatcher<str, 2> = all!(first_matcher, second_matcher);
+
+        verify_that!(
+            matcher.explain_match("A string"),
+            displays_as(eq("which does not start with \"Another\""))
+        )
+    }
+
+    #[test]
+    fn mismatch_description_is_simple_when_only_one_consistuent() -> Result<()> {
+        let first_matcher = starts_with("Another");
+        let matcher: internal::AllMatcher<str, 1> = all!(first_matcher);
+
+        verify_that!(
+            matcher.explain_match("A string"),
+            displays_as(eq("which does not start with \"Another\""))
+        )
+    }
+}
diff --git a/src/matchers/any_matcher.rs b/src/matchers/any_matcher.rs
new file mode 100644
index 0000000..82c8910
--- /dev/null
+++ b/src/matchers/any_matcher.rs
@@ -0,0 +1,199 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// There are no visible documentation elements in this module; the declarative
+// macro is documented at the top level.
+#![doc(hidden)]
+
+/// Matches a value which at least one of the given matchers match.
+///
+/// Each argument is a [`Matcher`][crate::matcher::Matcher] which matches
+/// against the actual value.
+///
+/// For example:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!("A string", any!(starts_with("A"), ends_with("string")))?; // Passes
+/// verify_that!("A string", any!(starts_with("A"), starts_with("string")))?; // Passes
+/// verify_that!("A string", any!(ends_with("A"), ends_with("string")))?; // Passes
+/// #     Ok(())
+/// # }
+/// # fn should_fail() -> Result<()> {
+/// verify_that!("A string", any!(starts_with("An"), ends_with("not a string")))?; // Fails
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # should_fail().unwrap_err();
+/// ```
+///
+/// Using this macro is equivalent to using the
+/// [`or`][crate::matcher::Matcher::or] method:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(10, gt(9).or(lt(8)))?; // Also passes
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// Assertion failure messages are not guaranteed to be identical, however.
+#[macro_export]
+macro_rules! any {
+    ($($matcher:expr),* $(,)?) => {{
+        use $crate::matchers::any_matcher::internal::AnyMatcher;
+        AnyMatcher::new([$(Box::new($matcher)),*])
+    }}
+}
+
+/// Functionality needed by the [`any`] macro.
+///
+/// For internal use only. API stablility is not guaranteed!
+#[doc(hidden)]
+pub mod internal {
+    use crate::matcher::{Matcher, MatcherResult};
+    use crate::matcher_support::description::Description;
+    use crate::matchers::anything;
+    use std::fmt::Debug;
+
+    /// A matcher which matches an input value matched by all matchers in the
+    /// array `components`.
+    ///
+    /// For internal use only. API stablility is not guaranteed!
+    #[doc(hidden)]
+    pub struct AnyMatcher<'a, T: Debug + ?Sized, const N: usize> {
+        components: [Box<dyn Matcher<ActualT = T> + 'a>; N],
+    }
+
+    impl<'a, T: Debug + ?Sized, const N: usize> AnyMatcher<'a, T, N> {
+        /// Constructs an [`AnyMatcher`] with the given component matchers.
+        ///
+        /// Intended for use only by the [`all`] macro.
+        pub fn new(components: [Box<dyn Matcher<ActualT = T> + 'a>; N]) -> Self {
+            Self { components }
+        }
+    }
+
+    impl<'a, T: Debug + ?Sized, const N: usize> Matcher for AnyMatcher<'a, T, N> {
+        type ActualT = T;
+
+        fn matches(&self, actual: &Self::ActualT) -> MatcherResult {
+            MatcherResult::from(self.components.iter().any(|c| c.matches(actual).is_match()))
+        }
+
+        fn explain_match(&self, actual: &Self::ActualT) -> String {
+            match N {
+                0 => format!("which {}", anything::<T>().describe(MatcherResult::NoMatch)),
+                1 => self.components[0].explain_match(actual),
+                _ => {
+                    let failures = self
+                        .components
+                        .iter()
+                        .filter(|component| component.matches(actual).is_no_match())
+                        .map(|component| component.explain_match(actual))
+                        .collect::<Description>();
+                    if failures.len() == 1 {
+                        format!("{}", failures)
+                    } else {
+                        format!("{}", failures.bullet_list().indent_except_first_line())
+                    }
+                }
+            }
+        }
+
+        fn describe(&self, matcher_result: MatcherResult) -> String {
+            match N {
+                0 => anything::<T>().describe(matcher_result),
+                1 => self.components[0].describe(matcher_result),
+                _ => {
+                    let properties = self
+                        .components
+                        .iter()
+                        .map(|m| m.describe(matcher_result))
+                        .collect::<Description>()
+                        .bullet_list()
+                        .indent();
+                    format!(
+                        "{}:\n{properties}",
+                        if matcher_result.into() {
+                            "has at least one of the following properties"
+                        } else {
+                            "has none of the following properties"
+                        }
+                    )
+                }
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::internal;
+    use crate::matcher::{Matcher, MatcherResult};
+    use crate::prelude::*;
+    use indoc::indoc;
+
+    #[test]
+    fn description_shows_more_than_one_matcher() -> Result<()> {
+        let first_matcher = starts_with("A");
+        let second_matcher = ends_with("string");
+        let matcher: internal::AnyMatcher<String, 2> = any!(first_matcher, second_matcher);
+
+        verify_that!(
+            matcher.describe(MatcherResult::Match),
+            eq(indoc!(
+                "
+                has at least one of the following properties:
+                  * starts with prefix \"A\"
+                  * ends with suffix \"string\""
+            ))
+        )
+    }
+
+    #[test]
+    fn description_shows_one_matcher_directly() -> Result<()> {
+        let first_matcher = starts_with("A");
+        let matcher: internal::AnyMatcher<String, 1> = any!(first_matcher);
+
+        verify_that!(matcher.describe(MatcherResult::Match), eq("starts with prefix \"A\""))
+    }
+
+    #[test]
+    fn mismatch_description_shows_which_matcher_failed_if_more_than_one_constituent() -> Result<()>
+    {
+        let first_matcher = starts_with("Another");
+        let second_matcher = ends_with("string");
+        let matcher: internal::AnyMatcher<str, 2> = any!(first_matcher, second_matcher);
+
+        verify_that!(
+            matcher.explain_match("A string"),
+            displays_as(eq("which does not start with \"Another\""))
+        )
+    }
+
+    #[test]
+    fn mismatch_description_is_simple_when_only_one_constituent() -> Result<()> {
+        let first_matcher = starts_with("Another");
+        let matcher: internal::AnyMatcher<str, 1> = any!(first_matcher);
+
+        verify_that!(
+            matcher.explain_match("A string"),
+            displays_as(eq("which does not start with \"Another\""))
+        )
+    }
+}
diff --git a/src/matchers/anything_matcher.rs b/src/matchers/anything_matcher.rs
new file mode 100644
index 0000000..82de460
--- /dev/null
+++ b/src/matchers/anything_matcher.rs
@@ -0,0 +1,78 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::{fmt::Debug, marker::PhantomData};
+
+/// Matches anything. This matcher always succeeds.
+///
+/// This is useful to check if `actual` matches the specific structure (like
+/// `Some(...)`)  but without caring about the internal value.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// let option = Some("Some value");
+/// verify_that!(option, some(anything()))?;
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+pub fn anything<T: Debug + ?Sized>() -> impl Matcher<ActualT = T> {
+    Anything::<T>(Default::default())
+}
+
+struct Anything<T: ?Sized>(PhantomData<T>);
+
+impl<T: Debug + ?Sized> Matcher for Anything<T> {
+    type ActualT = T;
+
+    fn matches(&self, _: &T) -> MatcherResult {
+        MatcherResult::Match
+    }
+
+    fn describe(&self, matcher_result: MatcherResult) -> String {
+        match matcher_result {
+            MatcherResult::Match => "is anything".to_string(),
+            MatcherResult::NoMatch => "never matches".to_string(),
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::anything;
+    use crate::prelude::*;
+
+    #[test]
+    fn anything_matches_i32() -> Result<()> {
+        let value = 32;
+        verify_that!(value, anything())?;
+        Ok(())
+    }
+
+    #[test]
+    fn anything_matches_str() -> Result<()> {
+        let value = "32";
+        verify_that!(value, anything())?;
+        Ok(())
+    }
+
+    #[test]
+    fn anything_matches_option() -> Result<()> {
+        let value = Some(32);
+        verify_that!(value, some(anything()))?;
+        Ok(())
+    }
+}
diff --git a/src/matchers/char_count_matcher.rs b/src/matchers/char_count_matcher.rs
new file mode 100644
index 0000000..a7765b4
--- /dev/null
+++ b/src/matchers/char_count_matcher.rs
@@ -0,0 +1,166 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::{fmt::Debug, marker::PhantomData};
+
+/// Matches a string whose number of Unicode scalars matches `expected`.
+///
+/// In other words, the argument must match the output of
+/// [`actual_string.chars().count()`][std::str::Chars].
+///
+/// This can have surprising effects when what appears to be a single character
+/// is composed of multiple Unicode scalars. See [Rust documentation on
+/// character
+/// representation](https://doc.rust-lang.org/std/primitive.char.html#representation)
+/// for more information.
+///
+/// This matches against owned strings and string slices.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// let string_slice = "A string";
+/// verify_that!(string_slice, char_count(eq(8)))?;
+/// let non_ascii_string_slice = "Ä ſtřiɲğ";
+/// verify_that!(non_ascii_string_slice, char_count(eq(8)))?;
+/// let owned_string = String::from("A string");
+/// verify_that!(owned_string, char_count(eq(8)))?;
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// The parameter `expected` can be any integer numeric matcher.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// let string_slice = "A string";
+/// verify_that!(string_slice, char_count(gt(4)))?;
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+pub fn char_count<T: Debug + ?Sized + AsRef<str>, E: Matcher<ActualT = usize>>(
+    expected: E,
+) -> impl Matcher<ActualT = T> {
+    CharLenMatcher { expected, phantom: Default::default() }
+}
+
+struct CharLenMatcher<T: ?Sized, E> {
+    expected: E,
+    phantom: PhantomData<T>,
+}
+
+impl<T: Debug + ?Sized + AsRef<str>, E: Matcher<ActualT = usize>> Matcher for CharLenMatcher<T, E> {
+    type ActualT = T;
+
+    fn matches(&self, actual: &T) -> MatcherResult {
+        self.expected.matches(&actual.as_ref().chars().count())
+    }
+
+    fn describe(&self, matcher_result: MatcherResult) -> String {
+        match matcher_result {
+            MatcherResult::Match => {
+                format!(
+                    "has character count, which {}",
+                    self.expected.describe(MatcherResult::Match)
+                )
+            }
+            MatcherResult::NoMatch => {
+                format!(
+                    "has character count, which {}",
+                    self.expected.describe(MatcherResult::NoMatch)
+                )
+            }
+        }
+    }
+
+    fn explain_match(&self, actual: &T) -> String {
+        let actual_size = actual.as_ref().chars().count();
+        format!(
+            "which has character count {}, {}",
+            actual_size,
+            self.expected.explain_match(&actual_size)
+        )
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::char_count;
+    use crate::matcher::{Matcher, MatcherResult};
+    use crate::prelude::*;
+    use indoc::indoc;
+    use std::fmt::Debug;
+    use std::marker::PhantomData;
+
+    #[test]
+    fn char_count_matches_string_slice() -> Result<()> {
+        let value = "abcd";
+        verify_that!(value, char_count(eq(4)))
+    }
+
+    #[test]
+    fn char_count_matches_owned_string() -> Result<()> {
+        let value = String::from("abcd");
+        verify_that!(value, char_count(eq(4)))
+    }
+
+    #[test]
+    fn char_count_counts_non_ascii_characters_correctly() -> Result<()> {
+        let value = "äöüß";
+        verify_that!(value, char_count(eq(4)))
+    }
+
+    #[test]
+    fn char_count_explains_match() -> Result<()> {
+        struct TestMatcher<T>(PhantomData<T>);
+        impl<T: Debug> Matcher for TestMatcher<T> {
+            type ActualT = T;
+
+            fn matches(&self, _: &T) -> MatcherResult {
+                false.into()
+            }
+
+            fn describe(&self, _: MatcherResult) -> String {
+                "called described".into()
+            }
+
+            fn explain_match(&self, _: &T) -> String {
+                "called explain_match".into()
+            }
+        }
+        verify_that!(
+            char_count(TestMatcher(Default::default())).explain_match(&"A string"),
+            displays_as(eq("which has character count 8, called explain_match"))
+        )
+    }
+
+    #[test]
+    fn char_count_has_correct_failure_message() -> Result<()> {
+        let result = verify_that!("äöüß", char_count(eq(3)));
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc!(
+                r#"
+                Value of: "äöüß"
+                Expected: has character count, which is equal to 3
+                Actual: "äöüß",
+                  which has character count 4, which isn't equal to 3"#
+            ))))
+        )
+    }
+}
diff --git a/src/matchers/conjunction_matcher.rs b/src/matchers/conjunction_matcher.rs
new file mode 100644
index 0000000..1ba59c3
--- /dev/null
+++ b/src/matchers/conjunction_matcher.rs
@@ -0,0 +1,157 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// There are no visible documentation elements in this module.
+#![doc(hidden)]
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::fmt::Debug;
+
+/// Matcher created by [`Matcher::and`].
+///
+/// **For internal use only. API stablility is not guaranteed!**
+#[doc(hidden)]
+pub struct ConjunctionMatcher<M1, M2> {
+    m1: M1,
+    m2: M2,
+}
+
+impl<M1, M2> ConjunctionMatcher<M1, M2> {
+    pub(crate) fn new(m1: M1, m2: M2) -> Self {
+        Self { m1, m2 }
+    }
+}
+
+impl<M1: Matcher, M2: Matcher<ActualT = M1::ActualT>> Matcher for ConjunctionMatcher<M1, M2>
+where
+    M1::ActualT: Debug,
+{
+    type ActualT = M1::ActualT;
+
+    fn matches(&self, actual: &M1::ActualT) -> MatcherResult {
+        match (self.m1.matches(actual), self.m2.matches(actual)) {
+            (MatcherResult::Match, MatcherResult::Match) => MatcherResult::Match,
+            _ => MatcherResult::NoMatch,
+        }
+    }
+
+    fn explain_match(&self, actual: &M1::ActualT) -> String {
+        match (self.m1.matches(actual), self.m2.matches(actual)) {
+            (MatcherResult::Match, MatcherResult::Match) => {
+                format!(
+                    "{} and\n  {}",
+                    self.m1.explain_match(actual),
+                    self.m2.explain_match(actual)
+                )
+            }
+            (MatcherResult::NoMatch, MatcherResult::Match) => self.m1.explain_match(actual),
+            (MatcherResult::Match, MatcherResult::NoMatch) => self.m2.explain_match(actual),
+            (MatcherResult::NoMatch, MatcherResult::NoMatch) => {
+                format!(
+                    "{} and\n  {}",
+                    self.m1.explain_match(actual),
+                    self.m2.explain_match(actual)
+                )
+            }
+        }
+    }
+
+    fn describe(&self, matcher_result: MatcherResult) -> String {
+        format!("{}, and {}", self.m1.describe(matcher_result), self.m2.describe(matcher_result))
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::prelude::*;
+    use indoc::indoc;
+
+    #[test]
+    fn and_true_true_matches() -> Result<()> {
+        verify_that!(1, anything().and(anything()))
+    }
+
+    #[test]
+    fn and_true_false_does_not_match() -> Result<()> {
+        let result = verify_that!(1, anything().and(not(anything())));
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc!(
+                "
+                Value of: 1
+                Expected: is anything, and never matches
+                Actual: 1,
+                  which is anything
+                "
+            ))))
+        )
+    }
+
+    #[test]
+    fn and_false_true_does_not_match() -> Result<()> {
+        let result = verify_that!(1, not(anything()).and(anything()));
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc!(
+                "
+                    Value of: 1
+                    Expected: never matches, and is anything
+                    Actual: 1,
+                      which is anything
+                "
+            ))))
+        )
+    }
+
+    #[test]
+    fn and_false_false_does_not_match() -> Result<()> {
+        let result = verify_that!(1, not(anything()).and(not(anything())));
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc!(
+                "
+                Value of: 1
+                Expected: never matches, and never matches
+                Actual: 1,
+                  which is anything and
+                  which is anything
+                "
+            ))))
+        )
+    }
+
+    #[test]
+    fn chained_and_matches() -> Result<()> {
+        #[derive(Debug)]
+        struct Struct {
+            a: i32,
+            b: i32,
+            c: i32,
+        }
+        verify_that!(
+            Struct { a: 1, b: 2, c: 3 },
+            field!(Struct.a, eq(1)).and(field!(Struct.b, eq(2))).and(field!(Struct.c, eq(3)))
+        )
+    }
+
+    #[test]
+    fn works_with_str_slices() -> Result<()> {
+        verify_that!("A string", starts_with("A").and(ends_with("string")))
+    }
+
+    #[test]
+    fn works_with_owned_strings() -> Result<()> {
+        verify_that!("A string".to_string(), starts_with("A").and(ends_with("string")))
+    }
+}
diff --git a/src/matchers/container_eq_matcher.rs b/src/matchers/container_eq_matcher.rs
new file mode 100644
index 0000000..f80cebf
--- /dev/null
+++ b/src/matchers/container_eq_matcher.rs
@@ -0,0 +1,312 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::fmt::Debug;
+use std::marker::PhantomData;
+
+/// Matches a container equal (in the sense of `==`) to `expected`.
+///
+/// This is similar to [`crate::matchers::eq`] except that an assertion failure
+/// message generated from this matcher will include the missing and unexpected
+/// items in the actual value, e.g.:
+///
+/// ```text
+/// Expected container to equal [1, 2, 3]
+///   but was: [1, 2, 4]
+///   Missing: [3]
+///   Unexpected: [4]
+/// ```
+///
+/// The type of `expected` must implement `IntoIterator` with an `Item` which
+/// implements `PartialEq`. If the container type is a `Vec`, then the expected
+/// type may be a slice of the same element type. For example:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// let vec = vec![1, 2, 3];
+/// verify_that!(vec, container_eq([1, 2, 3]))?;
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// As an exception, if the actual type is a `Vec<String>`, the expected type
+/// may be a slice of `&str`:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// let vec: Vec<String> = vec!["A string".into(), "Another string".into()];
+/// verify_that!(vec, container_eq(["A string", "Another string"]))?;
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// These exceptions allow one to avoid unnecessary allocations in test
+/// assertions.
+///
+/// One can also check container equality of a slice with an array. To do so,
+/// dereference the slice:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// let value = &[1, 2, 3];
+/// verify_that!(*value, container_eq([1, 2, 3]))?;
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// Otherwise, the actual and expected types must be identical.
+///
+/// *Performance note*: In the event of a mismatch leading to an assertion
+/// failure, the construction of the lists of missing and unexpected values
+/// uses a naive algorithm requiring time proportional to the product of the
+/// sizes of the expected and actual values. This should therefore only be used
+/// when the containers are small enough that this is not a problem.
+// This returns ContainerEqMatcher and not impl Matcher because
+// ContainerEqMatcher has some specialisations for slice types (see
+// documentation above). Returning impl Matcher would hide those from the
+// compiler.
+pub fn container_eq<ActualContainerT, ExpectedContainerT>(
+    expected: ExpectedContainerT,
+) -> ContainerEqMatcher<ActualContainerT, ExpectedContainerT>
+where
+    ActualContainerT: PartialEq<ExpectedContainerT> + Debug + ?Sized,
+    ExpectedContainerT: Debug,
+{
+    ContainerEqMatcher { expected, phantom: Default::default() }
+}
+
+pub struct ContainerEqMatcher<ActualContainerT: ?Sized, ExpectedContainerT> {
+    expected: ExpectedContainerT,
+    phantom: PhantomData<ActualContainerT>,
+}
+
+impl<ActualElementT, ActualContainerT, ExpectedElementT, ExpectedContainerT> Matcher
+    for ContainerEqMatcher<ActualContainerT, ExpectedContainerT>
+where
+    ActualElementT: PartialEq<ExpectedElementT> + Debug + ?Sized,
+    ActualContainerT: PartialEq<ExpectedContainerT> + Debug + ?Sized,
+    ExpectedElementT: Debug,
+    ExpectedContainerT: Debug,
+    for<'a> &'a ActualContainerT: IntoIterator<Item = &'a ActualElementT>,
+    for<'a> &'a ExpectedContainerT: IntoIterator<Item = &'a ExpectedElementT>,
+{
+    type ActualT = ActualContainerT;
+
+    fn matches(&self, actual: &ActualContainerT) -> MatcherResult {
+        (*actual == self.expected).into()
+    }
+
+    fn explain_match(&self, actual: &ActualContainerT) -> String {
+        build_explanation(self.get_missing_items(actual), self.get_unexpected_items(actual))
+    }
+
+    fn describe(&self, matcher_result: MatcherResult) -> String {
+        match matcher_result {
+            MatcherResult::Match => format!("is equal to {:?}", self.expected),
+            MatcherResult::NoMatch => format!("isn't equal to {:?}", self.expected),
+        }
+    }
+}
+
+impl<ActualElementT, ActualContainerT, ExpectedElementT, ExpectedContainerT>
+    ContainerEqMatcher<ActualContainerT, ExpectedContainerT>
+where
+    ActualElementT: PartialEq<ExpectedElementT> + ?Sized,
+    ActualContainerT: PartialEq<ExpectedContainerT> + ?Sized,
+    for<'a> &'a ActualContainerT: IntoIterator<Item = &'a ActualElementT>,
+    for<'a> &'a ExpectedContainerT: IntoIterator<Item = &'a ExpectedElementT>,
+{
+    fn get_missing_items(&self, actual: &ActualContainerT) -> Vec<&ExpectedElementT> {
+        self.expected.into_iter().filter(|&i| !actual.into_iter().any(|j| j == i)).collect()
+    }
+
+    fn get_unexpected_items<'a>(&self, actual: &'a ActualContainerT) -> Vec<&'a ActualElementT> {
+        actual.into_iter().filter(|&i| !self.expected.into_iter().any(|j| i == j)).collect()
+    }
+}
+
+fn build_explanation<T: Debug, U: Debug>(missing: Vec<T>, unexpected: Vec<U>) -> String {
+    match (missing.len(), unexpected.len()) {
+        // TODO(b/261175849) add more data here (out of order elements, duplicated elements, etc...)
+        (0, 0) => "which contains all the elements".to_string(),
+        (0, 1) => format!("which contains the unexpected element {:?}", unexpected[0]),
+        (0, _) => format!("which contains the unexpected elements {unexpected:?}",),
+        (1, 0) => format!("which is missing the element {:?}", missing[0]),
+        (1, 1) => {
+            format!(
+                "which is missing the element {:?} and contains the unexpected element {:?}",
+                missing[0], unexpected[0]
+            )
+        }
+        (1, _) => {
+            format!(
+                "which is missing the element {:?} and contains the unexpected elements {unexpected:?}",
+                missing[0]
+            )
+        }
+        (_, 0) => format!("which is missing the elements {missing:?}"),
+        (_, 1) => {
+            format!(
+                "which is missing the elements {missing:?} and contains the unexpected element {:?}",
+                unexpected[0]
+            )
+        }
+        (_, _) => {
+            format!(
+                "which is missing the elements {missing:?} and contains the unexpected elements {unexpected:?}",
+            )
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::container_eq;
+    use crate::matcher::{Matcher, MatcherResult};
+    use crate::prelude::*;
+    use indoc::indoc;
+    use std::collections::HashSet;
+
+    #[test]
+    fn container_eq_returns_match_when_containers_match() -> Result<()> {
+        verify_that!(vec![1, 2, 3], container_eq(vec![1, 2, 3]))
+    }
+
+    #[test]
+    fn container_eq_matches_array_with_slice() -> Result<()> {
+        let value = &[1, 2, 3];
+        verify_that!(*value, container_eq([1, 2, 3]))
+    }
+
+    #[test]
+    fn container_eq_matches_hash_set() -> Result<()> {
+        let value: HashSet<i32> = [1, 2, 3].into();
+        verify_that!(value, container_eq([1, 2, 3].into()))
+    }
+
+    #[test]
+    fn container_eq_full_error_message() -> Result<()> {
+        let result = verify_that!(vec![1, 3, 2], container_eq(vec![1, 2, 3]));
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc!(
+                "
+                    Value of: vec![1, 3, 2]
+                    Expected: is equal to [1, 2, 3]
+                    Actual: [1, 3, 2],
+                      which contains all the elements
+                "
+            ))))
+        )
+    }
+
+    #[test]
+    fn container_eq_returns_mismatch_when_elements_out_of_order() -> Result<()> {
+        verify_that!(
+            container_eq(vec![1, 2, 3]).explain_match(&vec![1, 3, 2]),
+            displays_as(eq("which contains all the elements"))
+        )
+    }
+
+    #[test]
+    fn container_eq_mismatch_shows_missing_elements_in_container() -> Result<()> {
+        verify_that!(
+            container_eq(vec![1, 2, 3]).explain_match(&vec![1, 2]),
+            displays_as(eq("which is missing the element 3"))
+        )
+    }
+
+    #[test]
+    fn container_eq_mismatch_shows_surplus_elements_in_container() -> Result<()> {
+        verify_that!(
+            container_eq(vec![1, 2]).explain_match(&vec![1, 2, 3]),
+            displays_as(eq("which contains the unexpected element 3"))
+        )
+    }
+
+    #[test]
+    fn container_eq_mismatch_shows_missing_and_surplus_elements_in_container() -> Result<()> {
+        verify_that!(
+            container_eq(vec![1, 2, 3]).explain_match(&vec![1, 2, 4]),
+            displays_as(eq("which is missing the element 3 and contains the unexpected element 4"))
+        )
+    }
+
+    #[test]
+    fn container_eq_mismatch_does_not_show_duplicated_element() -> Result<()> {
+        verify_that!(
+            container_eq(vec![1, 2, 3]).explain_match(&vec![1, 2, 3, 3]),
+            displays_as(eq("which contains all the elements"))
+        )
+    }
+
+    #[test]
+    fn container_eq_matches_owned_vec_with_array() -> Result<()> {
+        let vector = vec![123, 234];
+        verify_that!(vector, container_eq([123, 234]))
+    }
+
+    #[test]
+    fn container_eq_matches_owned_vec_of_owned_strings_with_slice_of_string_references()
+    -> Result<()> {
+        let vector = vec!["A string".to_string(), "Another string".to_string()];
+        verify_that!(vector, container_eq(["A string", "Another string"]))
+    }
+
+    #[test]
+    fn container_eq_matches_owned_vec_of_owned_strings_with_shorter_slice_of_string_references()
+    -> Result<()> {
+        let actual = vec!["A string".to_string(), "Another string".to_string()];
+        let matcher = container_eq(["A string"]);
+
+        let result = matcher.matches(&actual);
+
+        verify_that!(result, eq(MatcherResult::NoMatch))
+    }
+
+    #[test]
+    fn container_eq_mismatch_with_slice_shows_missing_elements_in_container() -> Result<()> {
+        verify_that!(
+            container_eq([1, 2, 3]).explain_match(&vec![1, 2]),
+            displays_as(eq("which is missing the element 3"))
+        )
+    }
+
+    #[test]
+    fn container_eq_mismatch_with_str_slice_shows_missing_elements_in_container() -> Result<()> {
+        verify_that!(
+            container_eq(["A", "B", "C"]).explain_match(&vec!["A".to_string(), "B".to_string()]),
+            displays_as(eq("which is missing the element \"C\""))
+        )
+    }
+
+    #[test]
+    fn container_eq_mismatch_with_str_slice_shows_surplus_elements_in_container() -> Result<()> {
+        verify_that!(
+            container_eq(["A", "B"]).explain_match(&vec![
+                "A".to_string(),
+                "B".to_string(),
+                "C".to_string()
+            ]),
+            displays_as(eq("which contains the unexpected element \"C\""))
+        )
+    }
+}
diff --git a/src/matchers/contains_matcher.rs b/src/matchers/contains_matcher.rs
new file mode 100644
index 0000000..d714c88
--- /dev/null
+++ b/src/matchers/contains_matcher.rs
@@ -0,0 +1,273 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::{fmt::Debug, marker::PhantomData};
+
+/// Matches an iterable type whose elements contain a value matched by `inner`.
+///
+/// By default, this matches a container with any number of elements matched
+/// by `inner`. Use the method [`ContainsMatcher::times`] to constrain the
+/// matched containers to a specific number of matching elements.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(["Some value"], contains(eq("Some value")))?;  // Passes
+/// verify_that!(vec!["Some value"], contains(eq("Some value")))?;  // Passes
+/// #     Ok(())
+/// # }
+/// # fn should_fail_1() -> Result<()> {
+/// verify_that!([] as [String; 0], contains(eq("Some value")))?;   // Fails
+/// #     Ok(())
+/// # }
+/// # fn should_fail_2() -> Result<()> {
+/// verify_that!(["Some value"], contains(eq("Some other value")))?;   // Fails
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # should_fail_1().unwrap_err();
+/// # should_fail_2().unwrap_err();
+/// ```
+pub fn contains<T, InnerMatcherT>(inner: InnerMatcherT) -> ContainsMatcher<T, InnerMatcherT> {
+    ContainsMatcher { inner, count: None, phantom: Default::default() }
+}
+
+/// A matcher which matches a container containing one or more elements a given
+/// inner [`Matcher`] matches.
+pub struct ContainsMatcher<T, InnerMatcherT> {
+    inner: InnerMatcherT,
+    count: Option<Box<dyn Matcher<ActualT = usize>>>,
+    phantom: PhantomData<T>,
+}
+
+impl<T, InnerMatcherT> ContainsMatcher<T, InnerMatcherT> {
+    /// Configures this instance to match containers which contain a number of
+    /// matching items matched by `count`.
+    ///
+    /// For example, to assert that exactly three matching items must be
+    /// present, use:
+    ///
+    /// ```ignore
+    /// contains(...).times(eq(3))
+    /// ```
+    ///
+    /// One can also use `times(eq(0))` to test for the *absence* of an item
+    /// matching the expected value.
+    pub fn times(mut self, count: impl Matcher<ActualT = usize> + 'static) -> Self {
+        self.count = Some(Box::new(count));
+        self
+    }
+}
+
+// TODO(hovinen): Revisit the trait bounds to see whether this can be made more
+//  flexible. Namely, the following doesn't compile currently:
+//
+//      let matcher = contains(eq(&42));
+//      let val = 42;
+//      let _ = matcher.matches(&vec![&val]);
+//
+//  because val is dropped before matcher but the trait bound requires that
+//  the argument to matches outlive the matcher. It works fine if one defines
+//  val before matcher.
+impl<T: Debug, InnerMatcherT: Matcher<ActualT = T>, ContainerT: Debug> Matcher
+    for ContainsMatcher<ContainerT, InnerMatcherT>
+where
+    for<'a> &'a ContainerT: IntoIterator<Item = &'a T>,
+{
+    type ActualT = ContainerT;
+
+    fn matches(&self, actual: &Self::ActualT) -> MatcherResult {
+        if let Some(count) = &self.count {
+            count.matches(&self.count_matches(actual))
+        } else {
+            for v in actual.into_iter() {
+                if self.inner.matches(v).into() {
+                    return MatcherResult::Match;
+                }
+            }
+            MatcherResult::NoMatch
+        }
+    }
+
+    fn explain_match(&self, actual: &Self::ActualT) -> String {
+        let count = self.count_matches(actual);
+        match (count, &self.count) {
+            (_, Some(_)) => format!("which contains {} matching elements", count),
+            (0, None) => "which does not contain a matching element".to_string(),
+            (_, None) => "which contains a matching element".to_string(),
+        }
+    }
+
+    fn describe(&self, matcher_result: MatcherResult) -> String {
+        match (matcher_result, &self.count) {
+            (MatcherResult::Match, Some(count)) => format!(
+                "contains n elements which {}\n  where n {}",
+                self.inner.describe(MatcherResult::Match),
+                count.describe(MatcherResult::Match)
+            ),
+            (MatcherResult::NoMatch, Some(count)) => format!(
+                "doesn't contain n elements which {}\n  where n {}",
+                self.inner.describe(MatcherResult::Match),
+                count.describe(MatcherResult::Match)
+            ),
+            (MatcherResult::Match, None) => format!(
+                "contains at least one element which {}",
+                self.inner.describe(MatcherResult::Match)
+            ),
+            (MatcherResult::NoMatch, None) => {
+                format!("contains no element which {}", self.inner.describe(MatcherResult::Match))
+            }
+        }
+    }
+}
+
+impl<ActualT, InnerMatcherT> ContainsMatcher<ActualT, InnerMatcherT> {
+    fn count_matches<T: Debug, ContainerT>(&self, actual: &ContainerT) -> usize
+    where
+        for<'b> &'b ContainerT: IntoIterator<Item = &'b T>,
+        InnerMatcherT: Matcher<ActualT = T>,
+    {
+        let mut count = 0;
+        for v in actual.into_iter() {
+            if self.inner.matches(v).into() {
+                count += 1;
+            }
+        }
+        count
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::{contains, ContainsMatcher};
+    use crate::matcher::{Matcher, MatcherResult};
+    use crate::prelude::*;
+
+    #[test]
+    fn contains_matches_singleton_slice_with_value() -> Result<()> {
+        let matcher = contains(eq(1));
+
+        let result = matcher.matches(&vec![1]);
+
+        verify_that!(result, eq(MatcherResult::Match))
+    }
+
+    #[test]
+    fn contains_matches_singleton_vec_with_value() -> Result<()> {
+        let matcher = contains(eq(1));
+
+        let result = matcher.matches(&vec![1]);
+
+        verify_that!(result, eq(MatcherResult::Match))
+    }
+
+    #[test]
+    fn contains_matches_two_element_slice_with_value() -> Result<()> {
+        let matcher = contains(eq(1));
+
+        let result = matcher.matches(&[0, 1]);
+
+        verify_that!(result, eq(MatcherResult::Match))
+    }
+
+    #[test]
+    fn contains_does_not_match_singleton_slice_with_wrong_value() -> Result<()> {
+        let matcher = contains(eq(1));
+
+        let result = matcher.matches(&[0]);
+
+        verify_that!(result, eq(MatcherResult::NoMatch))
+    }
+
+    #[test]
+    fn contains_does_not_match_empty_slice() -> Result<()> {
+        let matcher = contains(eq(1));
+
+        let result = matcher.matches(&[]);
+
+        verify_that!(result, eq(MatcherResult::NoMatch))
+    }
+
+    #[test]
+    fn contains_matches_slice_with_repeated_value() -> Result<()> {
+        let matcher = contains(eq(1)).times(eq(2));
+
+        let result = matcher.matches(&[1, 1]);
+
+        verify_that!(result, eq(MatcherResult::Match))
+    }
+
+    #[test]
+    fn contains_does_not_match_slice_with_too_few_of_value() -> Result<()> {
+        let matcher = contains(eq(1)).times(eq(2));
+
+        let result = matcher.matches(&[0, 1]);
+
+        verify_that!(result, eq(MatcherResult::NoMatch))
+    }
+
+    #[test]
+    fn contains_does_not_match_slice_with_too_many_of_value() -> Result<()> {
+        let matcher = contains(eq(1)).times(eq(1));
+
+        let result = matcher.matches(&[1, 1]);
+
+        verify_that!(result, eq(MatcherResult::NoMatch))
+    }
+
+    #[test]
+    fn contains_formats_without_multiplicity_by_default() -> Result<()> {
+        let matcher: ContainsMatcher<Vec<i32>, _> = contains(eq(1));
+
+        verify_that!(
+            Matcher::describe(&matcher, MatcherResult::Match),
+            eq("contains at least one element which is equal to 1")
+        )
+    }
+
+    #[test]
+    fn contains_formats_with_multiplicity_when_specified() -> Result<()> {
+        let matcher: ContainsMatcher<Vec<i32>, _> = contains(eq(1)).times(eq(2));
+
+        verify_that!(
+            Matcher::describe(&matcher, MatcherResult::Match),
+            eq("contains n elements which is equal to 1\n  where n is equal to 2")
+        )
+    }
+
+    #[test]
+    fn contains_mismatch_shows_number_of_times_element_was_found() -> Result<()> {
+        verify_that!(
+            contains(eq(3)).times(eq(1)).explain_match(&vec![1, 2, 3, 3]),
+            displays_as(eq("which contains 2 matching elements"))
+        )
+    }
+
+    #[test]
+    fn contains_mismatch_shows_when_matches() -> Result<()> {
+        verify_that!(
+            contains(eq(3)).explain_match(&vec![1, 2, 3, 3]),
+            displays_as(eq("which contains a matching element"))
+        )
+    }
+
+    #[test]
+    fn contains_mismatch_shows_when_no_matches() -> Result<()> {
+        verify_that!(
+            contains(eq(3)).explain_match(&vec![1, 2]),
+            displays_as(eq("which does not contain a matching element"))
+        )
+    }
+}
diff --git a/src/matchers/contains_regex_matcher.rs b/src/matchers/contains_regex_matcher.rs
new file mode 100644
index 0000000..8cc93a7
--- /dev/null
+++ b/src/matchers/contains_regex_matcher.rs
@@ -0,0 +1,148 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use regex::Regex;
+use std::fmt::Debug;
+use std::marker::PhantomData;
+use std::ops::Deref;
+
+/// Matches a string containing a substring which matches the given regular
+/// expression.
+///
+/// Both the actual value and the expected regular expression may be either a
+/// `String` or a string reference.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass_1() -> Result<()> {
+/// verify_that!("Some value", contains_regex("S.*e"))?;  // Passes
+/// #     Ok(())
+/// # }
+/// # fn should_fail() -> Result<()> {
+/// verify_that!("Another value", contains_regex("Some"))?;   // Fails
+/// #     Ok(())
+/// # }
+/// # fn should_pass_2() -> Result<()> {
+/// verify_that!("Some value".to_string(), contains_regex("v.*e"))?;   // Passes
+/// verify_that!("Some value", contains_regex("v.*e".to_string()))?;   // Passes
+/// #     Ok(())
+/// # }
+/// # should_pass_1().unwrap();
+/// # should_fail().unwrap_err();
+/// # should_pass_2().unwrap();
+/// ```
+///
+/// Panics if the given `pattern` is not a syntactically valid regular
+/// expression.
+// N.B. This returns the concrete type rather than an impl Matcher so that it
+// can act simultaneously as a Matcher<str> and a Matcher<String>. Otherwise the
+// compiler treats it as a Matcher<str> only and the code
+//   verify_that!("Some value".to_string(), contains_regex(".*value"))?;
+// doesn't compile.
+pub fn contains_regex<ActualT: ?Sized, PatternT: Deref<Target = str>>(
+    pattern: PatternT,
+) -> ContainsRegexMatcher<ActualT> {
+    ContainsRegexMatcher {
+        regex: Regex::new(pattern.deref()).unwrap(),
+        phantom: Default::default(),
+    }
+}
+
+/// A matcher matching a string-like type containing a substring matching a
+/// given regular expression.
+///
+/// Intended only to be used from the function [`contains_regex`] only.
+/// Should not be referenced by code outside this library.
+pub struct ContainsRegexMatcher<ActualT: ?Sized> {
+    regex: Regex,
+    phantom: PhantomData<ActualT>,
+}
+
+impl<ActualT: AsRef<str> + Debug + ?Sized> Matcher for ContainsRegexMatcher<ActualT> {
+    type ActualT = ActualT;
+
+    fn matches(&self, actual: &ActualT) -> MatcherResult {
+        self.regex.is_match(actual.as_ref()).into()
+    }
+
+    fn describe(&self, matcher_result: MatcherResult) -> String {
+        match matcher_result {
+            MatcherResult::Match => {
+                format!("contains the regular expression {:#?}", self.regex.as_str())
+            }
+            MatcherResult::NoMatch => {
+                format!("doesn't contain the regular expression {:#?}", self.regex.as_str())
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::{contains_regex, ContainsRegexMatcher};
+    use crate::matcher::{Matcher, MatcherResult};
+    use crate::prelude::*;
+
+    #[test]
+    fn contains_regex_matches_string_reference_with_pattern() -> Result<()> {
+        let matcher = contains_regex("S.*val");
+
+        let result = matcher.matches("Some value");
+
+        verify_that!(result, eq(MatcherResult::Match))
+    }
+
+    #[test]
+    fn contains_regex_does_not_match_string_without_pattern() -> Result<()> {
+        let matcher = contains_regex("Another");
+
+        let result = matcher.matches("Some value");
+
+        verify_that!(result, eq(MatcherResult::NoMatch))
+    }
+
+    #[test]
+    fn contains_regex_matches_owned_string_with_pattern() -> Result<()> {
+        let matcher = contains_regex("value");
+
+        let result = matcher.matches(&"Some value".to_string());
+
+        verify_that!(result, eq(MatcherResult::Match))
+    }
+
+    #[test]
+    fn contains_regex_matches_string_reference_with_owned_string() -> Result<()> {
+        let matcher = contains_regex("value");
+
+        let result = matcher.matches("Some value");
+
+        verify_that!(result, eq(MatcherResult::Match))
+    }
+
+    #[test]
+    fn verify_that_works_with_owned_string() -> Result<()> {
+        verify_that!("Some value".to_string(), contains_regex("value"))
+    }
+
+    #[test]
+    fn contains_regex_displays_quoted_debug_of_pattern() -> Result<()> {
+        let matcher: ContainsRegexMatcher<&str> = contains_regex("\n");
+
+        verify_that!(
+            Matcher::describe(&matcher, MatcherResult::Match),
+            eq("contains the regular expression \"\\n\"")
+        )
+    }
+}
diff --git a/src/matchers/disjunction_matcher.rs b/src/matchers/disjunction_matcher.rs
new file mode 100644
index 0000000..48bf226
--- /dev/null
+++ b/src/matchers/disjunction_matcher.rs
@@ -0,0 +1,109 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// There are no visible documentation elements in this module.
+#![doc(hidden)]
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::fmt::Debug;
+
+/// Matcher created by [`Matcher::or`].
+///
+/// **For internal use only. API stablility is not guaranteed!**
+#[doc(hidden)]
+pub struct DisjunctionMatcher<M1, M2> {
+    m1: M1,
+    m2: M2,
+}
+
+impl<M1, M2> DisjunctionMatcher<M1, M2> {
+    pub(crate) fn new(m1: M1, m2: M2) -> Self {
+        Self { m1, m2 }
+    }
+}
+
+impl<M1: Matcher, M2: Matcher<ActualT = M1::ActualT>> Matcher for DisjunctionMatcher<M1, M2>
+where
+    M1::ActualT: Debug,
+{
+    type ActualT = M1::ActualT;
+
+    fn matches(&self, actual: &M1::ActualT) -> MatcherResult {
+        match (self.m1.matches(actual), self.m2.matches(actual)) {
+            (MatcherResult::NoMatch, MatcherResult::NoMatch) => MatcherResult::NoMatch,
+            _ => MatcherResult::Match,
+        }
+    }
+
+    fn explain_match(&self, actual: &M1::ActualT) -> String {
+        format!("{} and\n  {}", self.m1.explain_match(actual), self.m2.explain_match(actual))
+    }
+
+    fn describe(&self, matcher_result: MatcherResult) -> String {
+        format!("{}, or {}", self.m1.describe(matcher_result), self.m2.describe(matcher_result))
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::prelude::*;
+    use indoc::indoc;
+
+    #[test]
+    fn or_true_true_matches() -> Result<()> {
+        verify_that!(1, anything().or(anything()))
+    }
+
+    #[test]
+    fn or_true_false_matches() -> Result<()> {
+        verify_that!(1, anything().or(not(anything())))
+    }
+
+    #[test]
+    fn or_false_true_matches() -> Result<()> {
+        verify_that!(1, not(anything()).or(anything()))
+    }
+
+    #[test]
+    fn or_false_false_does_not_match() -> Result<()> {
+        let result = verify_that!(1, not(anything()).or(not(anything())));
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc!(
+                "
+                Value of: 1
+                Expected: never matches, or never matches
+                Actual: 1,
+                  which is anything and
+                  which is anything
+                "
+            ))))
+        )
+    }
+
+    #[test]
+    fn chained_or_matches() -> Result<()> {
+        verify_that!(10, eq(1).or(eq(5)).or(ge(9)))
+    }
+
+    #[test]
+    fn works_with_str_slices() -> Result<()> {
+        verify_that!("A string", ends_with("A").or(ends_with("string")))
+    }
+
+    #[test]
+    fn works_with_owned_strings() -> Result<()> {
+        verify_that!("A string".to_string(), ends_with("A").or(ends_with("string")))
+    }
+}
diff --git a/src/matchers/display_matcher.rs b/src/matchers/display_matcher.rs
new file mode 100644
index 0000000..628769b
--- /dev/null
+++ b/src/matchers/display_matcher.rs
@@ -0,0 +1,119 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::fmt::{Debug, Display};
+use std::marker::PhantomData;
+
+/// Matches the string representation of types that implement `Display`.
+///
+/// ```ignore
+/// let result: impl Display = ...;
+/// verify_that!(result, displays_as(eq(format!("{}", result))))?;
+/// ```
+pub fn displays_as<T: Debug + Display, InnerMatcher: Matcher<ActualT = String>>(
+    inner: InnerMatcher,
+) -> impl Matcher<ActualT = T> {
+    DisplayMatcher::<T, _> { inner, phantom: Default::default() }
+}
+
+struct DisplayMatcher<T, InnerMatcher: Matcher> {
+    inner: InnerMatcher,
+    phantom: PhantomData<T>,
+}
+
+impl<T: Debug + Display, InnerMatcher: Matcher<ActualT = String>> Matcher
+    for DisplayMatcher<T, InnerMatcher>
+{
+    type ActualT = T;
+
+    fn matches(&self, actual: &T) -> MatcherResult {
+        self.inner.matches(&format!("{actual}"))
+    }
+
+    fn explain_match(&self, actual: &T) -> String {
+        format!("which displays as a string {}", self.inner.explain_match(&format!("{actual}")))
+    }
+
+    fn describe(&self, matcher_result: MatcherResult) -> String {
+        match matcher_result {
+            MatcherResult::Match => {
+                format!("displays as a string which {}", self.inner.describe(MatcherResult::Match))
+            }
+            MatcherResult::NoMatch => {
+                format!(
+                    "doesn't display as a string which {}",
+                    self.inner.describe(MatcherResult::Match)
+                )
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::displays_as;
+    use crate::prelude::*;
+    use indoc::indoc;
+    use std::fmt::{Debug, Display, Error, Formatter};
+
+    #[test]
+    fn display_matches_i32() -> Result<()> {
+        let value = 32;
+        verify_that!(value, displays_as(eq("32")))?;
+        Ok(())
+    }
+
+    #[test]
+    fn display_matches_str() -> Result<()> {
+        let value = "32";
+        verify_that!(value, displays_as(eq("32")))?;
+        Ok(())
+    }
+
+    #[test]
+    fn display_matches_struct() -> Result<()> {
+        #[allow(dead_code)]
+        #[derive(Debug)]
+        struct Struct {
+            a: i32,
+            b: i64,
+        }
+        impl Display for Struct {
+            fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), Error> {
+                write!(f, "{:?}", self)
+            }
+        }
+        verify_that!(Struct { a: 123, b: 321 }, displays_as(eq("Struct { a: 123, b: 321 }")))?;
+        Ok(())
+    }
+
+    #[test]
+    fn display_displays_error_message_with_explanation_from_inner_matcher() -> Result<()> {
+        let result = verify_that!("123\n234", displays_as(eq("123\n345")));
+
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc!(
+                "
+                    which displays as a string which isn't equal to \"123\\n345\"
+                    Difference(-actual / +expected):
+                     123
+                    -234
+                    +345
+                "
+            ))))
+        )
+    }
+}
diff --git a/src/matchers/each_matcher.rs b/src/matchers/each_matcher.rs
new file mode 100644
index 0000000..ce61207
--- /dev/null
+++ b/src/matchers/each_matcher.rs
@@ -0,0 +1,243 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use crate::matcher_support::description::Description;
+use std::{fmt::Debug, marker::PhantomData};
+
+/// Matches a container all of whose elements are matched by the matcher
+/// `inner`.
+///
+/// `T` can be any container such that `&T` implements `IntoIterator`.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # use std::collections::HashSet;
+/// # fn should_pass_1() -> Result<()> {
+/// let value = vec![1, 2, 3];
+/// verify_that!(value, each(gt(0)))?;  // Passes
+/// #     Ok(())
+/// # }
+/// # fn should_fail() -> Result<()> {
+/// #     let value = vec![1, 2, 3];
+/// verify_that!(value, each(lt(2)))?;  // Fails: 2 and 3 are not less than 2
+/// #     Ok(())
+/// # }
+///
+/// # fn should_pass_2() -> Result<()> {
+/// let value: HashSet<i32> = [1, 2, 3].into();
+/// verify_that!(value, each(gt(0)))?;  // Passes
+/// #     Ok(())
+/// # }
+/// # should_pass_1().unwrap();
+/// # should_fail().unwrap_err();
+/// # should_pass_2().unwrap();
+/// ```
+///
+/// One can also verify the contents of a slice by dereferencing it:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// let value = &[1, 2, 3];
+/// verify_that!(*value, each(gt(0)))?;
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+pub fn each<ElementT: Debug, ActualT: Debug + ?Sized, MatcherT>(
+    inner: MatcherT,
+) -> impl Matcher<ActualT = ActualT>
+where
+    for<'a> &'a ActualT: IntoIterator<Item = &'a ElementT>,
+    MatcherT: Matcher<ActualT = ElementT>,
+{
+    EachMatcher { inner, phantom: Default::default() }
+}
+
+struct EachMatcher<ActualT: ?Sized, MatcherT> {
+    inner: MatcherT,
+    phantom: PhantomData<ActualT>,
+}
+
+impl<ElementT: Debug, ActualT: Debug + ?Sized, MatcherT> Matcher for EachMatcher<ActualT, MatcherT>
+where
+    for<'a> &'a ActualT: IntoIterator<Item = &'a ElementT>,
+    MatcherT: Matcher<ActualT = ElementT>,
+{
+    type ActualT = ActualT;
+
+    fn matches(&self, actual: &ActualT) -> MatcherResult {
+        for element in actual {
+            if self.inner.matches(element).is_no_match() {
+                return MatcherResult::NoMatch;
+            }
+        }
+        MatcherResult::Match
+    }
+
+    fn explain_match(&self, actual: &ActualT) -> String {
+        let mut non_matching_elements = Vec::new();
+        for (index, element) in actual.into_iter().enumerate() {
+            if self.inner.matches(element).is_no_match() {
+                non_matching_elements.push((index, element, self.inner.explain_match(element)));
+            }
+        }
+        if non_matching_elements.is_empty() {
+            return format!("whose each element {}", self.inner.describe(MatcherResult::Match));
+        }
+        if non_matching_elements.len() == 1 {
+            let (idx, element, explanation) = non_matching_elements.remove(0);
+            return format!("whose element #{idx} is {element:?}, {explanation}");
+        }
+
+        let failed_indexes = non_matching_elements
+            .iter()
+            .map(|&(idx, _, _)| format!("#{idx}"))
+            .collect::<Vec<_>>()
+            .join(", ");
+        let element_explanations = non_matching_elements
+            .iter()
+            .map(|&(_, element, ref explanation)| format!("{element:?}, {explanation}"))
+            .collect::<Description>()
+            .indent();
+        format!("whose elements {failed_indexes} don't match\n{element_explanations}")
+    }
+
+    fn describe(&self, matcher_result: MatcherResult) -> String {
+        match matcher_result {
+            MatcherResult::Match => {
+                format!("only contains elements that {}", self.inner.describe(MatcherResult::Match))
+            }
+            MatcherResult::NoMatch => {
+                format!("contains no element that {}", self.inner.describe(MatcherResult::Match))
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::each;
+    use crate::prelude::*;
+    use indoc::indoc;
+    use std::collections::HashSet;
+
+    #[test]
+    fn each_matches_empty_vec() -> Result<()> {
+        let value: Vec<i32> = vec![];
+        verify_that!(value, each(gt(0)))
+    }
+
+    #[test]
+    fn each_matches_vec_with_one_element() -> Result<()> {
+        let value = vec![1];
+        verify_that!(value, each(gt(0)))
+    }
+
+    #[test]
+    fn each_matches_vec_with_two_elements() -> Result<()> {
+        let value = vec![1, 2];
+        verify_that!(value, each(gt(0)))
+    }
+
+    #[test]
+    fn each_matches_slice_with_one_element() -> Result<()> {
+        let value = &[1];
+        verify_that!(*value, each(gt(0)))
+    }
+
+    #[test]
+    fn each_matches_hash_set_with_one_element() -> Result<()> {
+        let value: HashSet<i32> = [1].into();
+        verify_that!(value, each(gt(0)))
+    }
+
+    #[test]
+    fn each_does_not_match_when_first_element_does_not_match() -> Result<()> {
+        let value = vec![0];
+        verify_that!(value, not(each(gt(1))))
+    }
+
+    #[test]
+    fn each_does_not_match_when_second_element_does_not_match() -> Result<()> {
+        let value = vec![2, 0];
+        verify_that!(value, not(each(gt(1))))
+    }
+
+    #[test]
+    fn each_shows_correct_message_when_first_item_does_not_match() -> Result<()> {
+        let result = verify_that!(vec![0, 2, 3], each(gt(0)));
+
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc!(
+                "
+                Value of: vec![0, 2, 3]
+                Expected: only contains elements that is greater than 0
+                Actual: [0, 2, 3],
+                  whose element #0 is 0, which is less than or equal to 0"
+            ))))
+        )
+    }
+
+    #[test]
+    fn each_shows_correct_message_when_second_item_does_not_match() -> Result<()> {
+        let result = verify_that!(vec![1, 0, 3], each(gt(0)));
+
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc!(
+                "
+                Value of: vec![1, 0, 3]
+                Expected: only contains elements that is greater than 0
+                Actual: [1, 0, 3],
+                  whose element #1 is 0, which is less than or equal to 0"
+            ))))
+        )
+    }
+
+    #[test]
+    fn each_shows_correct_message_when_first_two_items_do_not_match() -> Result<()> {
+        let result = verify_that!(vec![0, 1, 3], each(gt(1)));
+
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc!(
+                "
+                Value of: vec![0, 1, 3]
+                Expected: only contains elements that is greater than 1
+                Actual: [0, 1, 3],
+                  whose elements #0, #1 don't match
+                  0, which is less than or equal to 1
+                  1, which is less than or equal to 1"
+            ))))
+        )
+    }
+    #[test]
+    fn each_shows_inner_explanation() -> Result<()> {
+        let result = verify_that!(vec![vec![1, 2], vec![1]], each(each(eq(1))));
+
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc!(
+                "
+                Value of: vec![vec! [1, 2], vec! [1]]
+                Expected: only contains elements that only contains elements that is equal to 1
+                Actual: [[1, 2], [1]],
+                  whose element #0 is [1, 2], whose element #1 is 2, which isn't equal to 1"
+            ))))
+        )
+    }
+}
diff --git a/src/matchers/elements_are_matcher.rs b/src/matchers/elements_are_matcher.rs
new file mode 100644
index 0000000..736bf47
--- /dev/null
+++ b/src/matchers/elements_are_matcher.rs
@@ -0,0 +1,174 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// There are no visible documentation elements in this module; the declarative
+// macro is documented at the top level.
+#![doc(hidden)]
+
+/// Matches a container's elements to each matcher in order.
+///
+/// This macro produces a matcher against a container. It takes as arguments a
+/// sequence of matchers each of which should respectively match the
+/// corresponding element of the actual value.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// verify_that!(vec![1, 2, 3], elements_are![eq(1), anything(), gt(0).and(lt(123))])
+/// #    .unwrap();
+/// ```
+///
+/// The actual value must be a container implementing [`IntoIterator`]. This
+/// includes standard containers, slices (when dereferenced) and arrays.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// let vector = vec![1, 2, 3];
+/// let slice = vector.as_slice();
+/// verify_that!(*slice, elements_are![eq(1), anything(), gt(0).and(lt(123))])
+/// #    .unwrap();
+/// ```
+///
+/// This can also be omitted in [`verify_that!`] macros and replaced with square
+/// brackets.
+///
+/// ```
+/// # use googletest::prelude::*;
+///  verify_that!(vec![1, 2], [eq(1), eq(2)])
+/// #     .unwrap();
+/// ```
+///
+/// Note: This behavior is only possible in [`verify_that!`] macros. In any
+/// other cases, it is still necessary to use the
+/// [`elements_are!`][crate::elements_are] macro.
+///
+/// ```compile_fail
+/// # use googletest::prelude::*;
+/// verify_that!(vec![vec![1,2], vec![3]], [[eq(1), eq(2)], [eq(3)]])
+/// # .unwrap();
+/// ```
+///
+/// Use this instead:
+/// ```
+/// # use googletest::prelude::*;
+/// verify_that!(vec![vec![1,2], vec![3]], [elements_are![eq(1), eq(2)], elements_are![eq(3)]])
+/// # .unwrap();
+/// ```
+///
+/// This matcher does not support matching directly against an [`Iterator`]. To
+/// match against an iterator, use [`Iterator::collect`] to build a [`Vec`].
+///
+/// Do not use this with unordered containers, since that will lead to flaky
+/// tests. Use [`unordered_elements_are!`][crate::unordered_elements_are]
+/// instead.
+///
+/// [`IntoIterator`]: std::iter::IntoIterator
+/// [`Iterator`]: std::iter::Iterator
+/// [`Iterator::collect`]: std::iter::Iterator::collect
+/// [`Vec`]: std::vec::Vec
+#[macro_export]
+macro_rules! elements_are {
+    ($($matcher:expr),* $(,)?) => {{
+        use $crate::matchers::elements_are_matcher::internal::ElementsAre;
+        ElementsAre::new(vec![$(Box::new($matcher)),*])
+    }}
+}
+
+/// Module for use only by the procedural macros in this module.
+///
+/// **For internal use only. API stablility is not guaranteed!**
+#[doc(hidden)]
+pub mod internal {
+    use crate::matcher::{Matcher, MatcherResult};
+    use crate::matcher_support::description::Description;
+    use crate::matcher_support::zipped_iterator::zip;
+    use std::{fmt::Debug, marker::PhantomData};
+
+    /// This struct is meant to be used only by the macro `elements_are!`.
+    ///
+    /// **For internal use only. API stablility is not guaranteed!**
+    #[doc(hidden)]
+    pub struct ElementsAre<'a, ContainerT: ?Sized, T: Debug> {
+        elements: Vec<Box<dyn Matcher<ActualT = T> + 'a>>,
+        phantom: PhantomData<ContainerT>,
+    }
+
+    impl<'a, ContainerT: ?Sized, T: Debug> ElementsAre<'a, ContainerT, T> {
+        /// Factory only intended for use in the macro `elements_are!`.
+        ///
+        /// **For internal use only. API stablility is not guaranteed!**
+        #[doc(hidden)]
+        pub fn new(elements: Vec<Box<dyn Matcher<ActualT = T> + 'a>>) -> Self {
+            Self { elements, phantom: Default::default() }
+        }
+    }
+
+    impl<'a, T: Debug, ContainerT: Debug + ?Sized> Matcher for ElementsAre<'a, ContainerT, T>
+    where
+        for<'b> &'b ContainerT: IntoIterator<Item = &'b T>,
+    {
+        type ActualT = ContainerT;
+
+        fn matches(&self, actual: &ContainerT) -> MatcherResult {
+            let mut zipped_iterator = zip(actual.into_iter(), self.elements.iter());
+            for (a, e) in zipped_iterator.by_ref() {
+                if e.matches(a).is_no_match() {
+                    return MatcherResult::NoMatch;
+                }
+            }
+            if !zipped_iterator.has_size_mismatch() {
+                MatcherResult::Match
+            } else {
+                MatcherResult::NoMatch
+            }
+        }
+
+        fn explain_match(&self, actual: &ContainerT) -> String {
+            let actual_iterator = actual.into_iter();
+            let mut zipped_iterator = zip(actual_iterator, self.elements.iter());
+            let mut mismatches = Vec::new();
+            for (idx, (a, e)) in zipped_iterator.by_ref().enumerate() {
+                if e.matches(a).is_no_match() {
+                    mismatches.push(format!("element #{idx} is {a:?}, {}", e.explain_match(a)));
+                }
+            }
+            if mismatches.is_empty() {
+                if !zipped_iterator.has_size_mismatch() {
+                    "whose elements all match".to_string()
+                } else {
+                    format!("whose size is {}", zipped_iterator.left_size())
+                }
+            } else if mismatches.len() == 1 {
+                let mismatches = mismatches.into_iter().collect::<Description>();
+                format!("where {mismatches}")
+            } else {
+                let mismatches = mismatches.into_iter().collect::<Description>();
+                format!("where:\n{}", mismatches.bullet_list().indent())
+            }
+        }
+
+        fn describe(&self, matcher_result: MatcherResult) -> String {
+            format!(
+                "{} elements:\n{}",
+                if matcher_result.into() { "has" } else { "doesn't have" },
+                &self
+                    .elements
+                    .iter()
+                    .map(|matcher| matcher.describe(MatcherResult::Match))
+                    .collect::<Description>()
+                    .enumerate()
+                    .indent()
+            )
+        }
+    }
+}
diff --git a/src/matchers/empty_matcher.rs b/src/matchers/empty_matcher.rs
new file mode 100644
index 0000000..afefcb7
--- /dev/null
+++ b/src/matchers/empty_matcher.rs
@@ -0,0 +1,103 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::{fmt::Debug, marker::PhantomData};
+
+/// Matches an empty container.
+///
+/// `T` can be any container such that `&T` implements `IntoIterator`.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # use std::collections::HashSet;
+/// # fn should_pass() -> Result<()> {
+/// let value: Vec<i32> = vec![];
+/// verify_that!(value, empty())?;
+/// let value: HashSet<i32> = HashSet::new();
+/// verify_that!(value, empty())?;
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// One can also check whether a slice is empty by dereferencing it:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # use std::collections::HashSet;
+/// # fn should_pass() -> Result<()> {
+/// let value: &[u32] = &[];
+/// verify_that!(*value, empty())?;
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+
+pub fn empty<T: Debug + ?Sized>() -> impl Matcher<ActualT = T>
+where
+    for<'a> &'a T: IntoIterator,
+{
+    EmptyMatcher { phantom: Default::default() }
+}
+
+struct EmptyMatcher<T: ?Sized> {
+    phantom: PhantomData<T>,
+}
+
+impl<T: Debug + ?Sized> Matcher for EmptyMatcher<T>
+where
+    for<'a> &'a T: IntoIterator,
+{
+    type ActualT = T;
+
+    fn matches(&self, actual: &T) -> MatcherResult {
+        actual.into_iter().next().is_none().into()
+    }
+
+    fn describe(&self, matcher_result: MatcherResult) -> String {
+        if matcher_result.into() { "is empty" } else { "isn't empty" }.to_string()
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::empty;
+    use crate::prelude::*;
+    use std::collections::HashSet;
+
+    #[test]
+    fn empty_matcher_match_empty_vec() -> Result<()> {
+        let value: Vec<i32> = vec![];
+        verify_that!(value, empty())
+    }
+
+    #[test]
+    fn empty_matcher_does_not_match_empty_vec() -> Result<()> {
+        let value = vec![1, 2, 3];
+        verify_that!(value, not(empty()))
+    }
+
+    #[test]
+    fn empty_matcher_matches_empty_slice() -> Result<()> {
+        let value: &[i32] = &[];
+        verify_that!(*value, empty())
+    }
+
+    #[test]
+    fn empty_matcher_matches_empty_hash_set() -> Result<()> {
+        let value: HashSet<i32> = HashSet::new();
+        verify_that!(value, empty())
+    }
+}
diff --git a/src/matchers/eq_deref_of_matcher.rs b/src/matchers/eq_deref_of_matcher.rs
new file mode 100644
index 0000000..1540905
--- /dev/null
+++ b/src/matchers/eq_deref_of_matcher.rs
@@ -0,0 +1,151 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::{
+    matcher::{Matcher, MatcherResult},
+    matcher_support::{edit_distance, summarize_diff::create_diff},
+};
+use std::{fmt::Debug, marker::PhantomData, ops::Deref};
+
+/// Matches a value equal (in the sense of `==`) to the dereferenced value of
+/// `expected`.
+///
+/// This is similar to [`eq`][crate::matchers::eq] but takes a reference or
+/// smart pointer to the expected value rather than consuming it. This is useful
+/// when:
+///
+///  * one has only a reference to the expected value, and
+///  * the expected value cannot or should not be copied or cloned to create an
+///    owned value from it.
+///
+/// ```
+/// # use googletest::{matchers::eq_deref_of, verify_that};
+/// #[derive(Debug, PartialEq)]
+/// struct NonCloneableStruct(i32);
+/// let expected = NonCloneableStruct(123);
+/// verify_that!(NonCloneableStruct(123), eq_deref_of(&expected))
+/// #    .unwrap()
+/// ```
+///
+/// **Note**: while one can use `eq_deref_of` with the configuration methods of
+/// [`StrMatcherConfigurator`][crate::matchers::str_matcher::StrMatcherConfigurator]
+/// to configure string equality, it is not possible to do so when the input is
+/// a smart pointer to a string.
+///
+/// ```compile_fail
+/// # use googletest::{matchers::{eq_deref_of, str_matcher::StrMatcherConfigurator}, verify_that};
+/// verify_that!("A string", eq_deref_of(Box::new("A STRING")).ignoring_ascii_case()) // Does not compile
+/// #    .unwrap()
+/// ```
+///
+/// Otherwise, this has the same behaviour as [`eq`][crate::matchers::eq].
+pub fn eq_deref_of<ActualT: ?Sized, ExpectedRefT>(
+    expected: ExpectedRefT,
+) -> EqDerefOfMatcher<ActualT, ExpectedRefT> {
+    EqDerefOfMatcher { expected, phantom: Default::default() }
+}
+
+/// A matcher which matches a value equal to the derefenced value of `expected`.
+///
+/// See [`eq_deref_of`].
+pub struct EqDerefOfMatcher<ActualT: ?Sized, ExpectedRefT> {
+    pub(crate) expected: ExpectedRefT,
+    phantom: PhantomData<ActualT>,
+}
+
+impl<ActualT, ExpectedRefT, ExpectedT> Matcher for EqDerefOfMatcher<ActualT, ExpectedRefT>
+where
+    ActualT: Debug + ?Sized,
+    ExpectedRefT: Deref<Target = ExpectedT> + Debug,
+    ExpectedT: PartialEq<ActualT> + Debug,
+{
+    type ActualT = ActualT;
+
+    fn matches(&self, actual: &ActualT) -> MatcherResult {
+        (self.expected.deref() == actual).into()
+    }
+
+    fn describe(&self, matcher_result: MatcherResult) -> String {
+        match matcher_result {
+            MatcherResult::Match => format!("is equal to {:?}", self.expected),
+            MatcherResult::NoMatch => format!("isn't equal to {:?}", self.expected),
+        }
+    }
+
+    fn explain_match(&self, actual: &ActualT) -> String {
+        format!(
+            "which {}{}",
+            &self.describe(self.matches(actual)),
+            create_diff(
+                &format!("{:#?}", actual),
+                &format!("{:#?}", self.expected.deref()),
+                edit_distance::Mode::Exact,
+            )
+        )
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::eq_deref_of;
+    use crate::prelude::*;
+    use indoc::indoc;
+
+    #[derive(Debug, PartialEq)]
+    struct NonCloneNonCopyStruct(i32);
+
+    #[test]
+    fn matches_value_with_ref_to_equal_value() -> Result<()> {
+        verify_that!(NonCloneNonCopyStruct(123), eq_deref_of(&NonCloneNonCopyStruct(123)))
+    }
+
+    #[test]
+    fn matches_value_with_box_of_equal_value() -> Result<()> {
+        verify_that!(NonCloneNonCopyStruct(123), eq_deref_of(Box::new(NonCloneNonCopyStruct(123))))
+    }
+
+    #[test]
+    fn does_not_match_value_with_non_equal_value() -> Result<()> {
+        verify_that!(NonCloneNonCopyStruct(123), not(eq_deref_of(&NonCloneNonCopyStruct(234))))
+    }
+
+    #[test]
+    fn shows_structured_diff() -> Result<()> {
+        #[derive(Debug, PartialEq)]
+        struct Strukt {
+            int: i32,
+            string: String,
+        }
+
+        let result = verify_that!(
+            Strukt { int: 123, string: "something".into() },
+            eq_deref_of(Box::new(Strukt { int: 321, string: "someone".into() }))
+        );
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc! {
+            "
+            Actual: Strukt { int: 123, string: \"something\" },
+              which isn't equal to Strukt { int: 321, string: \"someone\" }
+            Difference(-actual / +expected):
+             Strukt {
+            -    int: 123,
+            +    int: 321,
+            -    string: \"something\",
+            +    string: \"someone\",
+             }
+            "})))
+        )
+    }
+}
diff --git a/src/matchers/eq_matcher.rs b/src/matchers/eq_matcher.rs
new file mode 100644
index 0000000..42684c9
--- /dev/null
+++ b/src/matchers/eq_matcher.rs
@@ -0,0 +1,405 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use crate::matcher_support::edit_distance;
+use crate::matcher_support::summarize_diff::create_diff;
+
+use std::{fmt::Debug, marker::PhantomData};
+
+/// Matches a value equal (in the sense of `==`) to `expected`.
+///
+/// The type of `expected` must implement the [`PartialEq`] trait so that the
+/// expected and actual values can be compared.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(123, eq(123))?; // Passes
+/// #     Ok(())
+/// # }
+/// # fn should_fail() -> Result<()> {
+/// verify_that!(123, eq(234))?; // Fails
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # should_fail().unwrap_err();
+/// ```
+///
+/// `expected` to `actual` must be comparable with one another via the
+/// [`PartialEq`] trait. In most cases, this means that they must be of the same
+/// type. However, there are a few cases where different but closely related
+/// types are comparable, for example `String` with `&str`.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(String::from("Some value"), eq("Some value"))?; // Passes
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// In most cases however, one must convert one of the arguments explicitly.
+/// This can be surprising when comparing integer types or references.
+///
+/// ```compile_fail
+/// verify_that!(123u32, eq(123u64))?; // Does not compile
+/// verify_that!(123u32 as u64, eq(123u64))?; // Passes
+/// ```
+///
+/// ```ignore
+/// let actual: &T = ...;
+/// let expected: T = T{...};
+/// verify_that(actual, eq(expected))?; // Does not compile
+/// verify_that(actual, eq(&expected))?; // Compiles
+/// ```
+///
+/// When matching with string types (`&str` and `String`), one can set more
+/// options on how equality is checked through the
+/// [`StrMatcherConfigurator`][crate::matchers::str_matcher::StrMatcherConfigurator]
+/// extension trait, which is implemented for this matcher.
+pub fn eq<A: ?Sized, T>(expected: T) -> EqMatcher<A, T> {
+    EqMatcher { expected, phantom: Default::default() }
+}
+
+/// A matcher which matches a value equal to `expected`.
+///
+/// See [`eq`].
+pub struct EqMatcher<A: ?Sized, T> {
+    pub(crate) expected: T,
+    phantom: PhantomData<A>,
+}
+
+impl<A: Debug + ?Sized, T: PartialEq<A> + Debug> Matcher for EqMatcher<A, T> {
+    type ActualT = A;
+
+    fn matches(&self, actual: &A) -> MatcherResult {
+        (self.expected == *actual).into()
+    }
+
+    fn describe(&self, matcher_result: MatcherResult) -> String {
+        match matcher_result {
+            MatcherResult::Match => format!("is equal to {:?}", self.expected),
+            MatcherResult::NoMatch => format!("isn't equal to {:?}", self.expected),
+        }
+    }
+
+    fn explain_match(&self, actual: &A) -> String {
+        let expected_debug = format!("{:#?}", self.expected);
+        let actual_debug = format!("{:#?}", actual);
+        let description = self.describe(self.matches(actual));
+
+        let diff = if is_multiline_string_debug(&actual_debug)
+            && is_multiline_string_debug(&expected_debug)
+        {
+            create_diff(
+                // The two calls below return None if and only if the strings expected_debug
+                // respectively actual_debug are not enclosed in ". The calls to
+                // is_multiline_string_debug above ensure that they are. So the calls cannot
+                // actually return None and unwrap() should not panic.
+                &to_display_output(&actual_debug).unwrap(),
+                &to_display_output(&expected_debug).unwrap(),
+                edit_distance::Mode::Exact,
+            )
+        } else {
+            create_diff(&actual_debug, &expected_debug, edit_distance::Mode::Exact)
+        };
+
+        format!("which {description}{diff}")
+    }
+}
+
+fn is_multiline_string_debug(string: &str) -> bool {
+    string.starts_with('"')
+        && string.ends_with('"')
+        && !string.contains('\n')
+        && string.contains("\\n")
+}
+
+fn to_display_output(string: &str) -> Option<String> {
+    Some(string.strip_prefix('"')?.strip_suffix('"')?.split("\\n").collect::<Vec<_>>().join("\n"))
+}
+
+#[cfg(test)]
+mod tests {
+    use super::eq;
+    use crate::prelude::*;
+    use indoc::indoc;
+
+    #[test]
+    fn eq_matches_string_reference_with_string_reference() -> Result<()> {
+        verify_that!("A string", eq("A string"))
+    }
+
+    #[test]
+    fn eq_matches_owned_string_with_string_reference() -> Result<()> {
+        let value = "A string".to_string();
+        verify_that!(value, eq("A string"))
+    }
+
+    #[test]
+    fn eq_matches_owned_string_reference_with_string_reference() -> Result<()> {
+        let value = "A string".to_string();
+        verify_that!(&value, eq("A string"))
+    }
+
+    #[test]
+    fn eq_matches_i32_with_i32() -> Result<()> {
+        verify_that!(123, eq(123))
+    }
+
+    #[test]
+    fn eq_struct_debug_diff() -> Result<()> {
+        #[derive(Debug, PartialEq)]
+        struct Strukt {
+            int: i32,
+            string: String,
+        }
+
+        let result = verify_that!(
+            Strukt { int: 123, string: "something".into() },
+            eq(Strukt { int: 321, string: "someone".into() })
+        );
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc! {
+            "
+            Actual: Strukt { int: 123, string: \"something\" },
+              which isn't equal to Strukt { int: 321, string: \"someone\" }
+            Difference(-actual / +expected):
+             Strukt {
+            -    int: 123,
+            +    int: 321,
+            -    string: \"something\",
+            +    string: \"someone\",
+             }
+            "})))
+        )
+    }
+
+    #[test]
+    fn eq_vec_debug_diff() -> Result<()> {
+        let result = verify_that!(vec![1, 2, 3], eq(vec![1, 3, 4]));
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc! {
+            "
+            Value of: vec![1, 2, 3]
+            Expected: is equal to [1, 3, 4]
+            Actual: [1, 2, 3],
+              which isn't equal to [1, 3, 4]
+            Difference(-actual / +expected):
+             [
+                 1,
+            -    2,
+                 3,
+            +    4,
+             ]
+            "})))
+        )
+    }
+
+    #[test]
+    fn eq_vec_debug_diff_length_mismatch() -> Result<()> {
+        let result = verify_that!(vec![1, 2, 3, 4, 5], eq(vec![1, 3, 5]));
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc! {
+            "
+            Value of: vec![1, 2, 3, 4, 5]
+            Expected: is equal to [1, 3, 5]
+            Actual: [1, 2, 3, 4, 5],
+              which isn't equal to [1, 3, 5]
+            Difference(-actual / +expected):
+             [
+                 1,
+            -    2,
+                 3,
+            -    4,
+                 5,
+             ]
+            "})))
+        )
+    }
+
+    #[test]
+    fn eq_debug_diff_common_lines_omitted() -> Result<()> {
+        let result = verify_that!((1..50).collect::<Vec<_>>(), eq((3..52).collect::<Vec<_>>()));
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc! {
+            "
+            Difference(-actual / +expected):
+             [
+            -    1,
+            -    2,
+                 3,
+                 4,
+             <---- 43 common lines omitted ---->
+                 48,
+                 49,
+            +    50,
+            +    51,
+             ]"})))
+        )
+    }
+
+    #[test]
+    fn eq_debug_diff_5_common_lines_not_omitted() -> Result<()> {
+        let result = verify_that!((1..8).collect::<Vec<_>>(), eq((3..10).collect::<Vec<_>>()));
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc! {
+            "
+            Difference(-actual / +expected):
+             [
+            -    1,
+            -    2,
+                 3,
+                 4,
+                 5,
+                 6,
+                 7,
+            +    8,
+            +    9,
+             ]"})))
+        )
+    }
+
+    #[test]
+    fn eq_debug_diff_start_common_lines_omitted() -> Result<()> {
+        let result = verify_that!((1..50).collect::<Vec<_>>(), eq((1..52).collect::<Vec<_>>()));
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc! {
+            "
+            Difference(-actual / +expected):
+             [
+                 1,
+             <---- 46 common lines omitted ---->
+                 48,
+                 49,
+            +    50,
+            +    51,
+             ]"})))
+        )
+    }
+
+    #[test]
+    fn eq_debug_diff_end_common_lines_omitted() -> Result<()> {
+        let result = verify_that!((1..52).collect::<Vec<_>>(), eq((3..52).collect::<Vec<_>>()));
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc! {
+            "
+            Difference(-actual / +expected):
+             [
+            -    1,
+            -    2,
+                 3,
+                 4,
+             <---- 46 common lines omitted ---->
+                 51,
+             ]"})))
+        )
+    }
+
+    #[test]
+    fn eq_multi_line_string_debug_diff() -> Result<()> {
+        let result = verify_that!("One\nTwo\nThree", eq("One\nSix\nThree"));
+        // TODO: b/257454450 - Make this more useful, by potentially unescaping the
+        // line return.
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc! {
+            r#"
+            Value of: "One\nTwo\nThree"
+            Expected: is equal to "One\nSix\nThree"
+            Actual: "One\nTwo\nThree",
+              which isn't equal to "One\nSix\nThree"
+            "#})))
+        )
+    }
+
+    #[test]
+    fn match_explanation_contains_diff_of_strings_if_more_than_one_line() -> Result<()> {
+        let result = verify_that!(
+            indoc!(
+                "
+                    First line
+                    Second line
+                    Third line
+                "
+            ),
+            eq(indoc!(
+                "
+                    First line
+                    Second lines
+                    Third line
+                "
+            ))
+        );
+
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc!(
+                "
+                 First line
+                -Second line
+                +Second lines
+                 Third line
+                "
+            ))))
+        )
+    }
+
+    #[test]
+    fn match_explanation_does_not_show_diff_if_actual_value_is_single_line() -> Result<()> {
+        let result = verify_that!(
+            "First line",
+            eq(indoc!(
+                "
+                    First line
+                    Second line
+                    Third line
+                "
+            ))
+        );
+
+        verify_that!(
+            result,
+            err(displays_as(not(contains_substring("Difference(-actual / +expected):"))))
+        )
+    }
+
+    #[test]
+    fn match_explanation_does_not_show_diff_if_expected_value_is_single_line() -> Result<()> {
+        let result = verify_that!(
+            indoc!(
+                "
+                    First line
+                    Second line
+                    Third line
+                "
+            ),
+            eq("First line")
+        );
+
+        verify_that!(
+            result,
+            err(displays_as(not(contains_substring("Difference(-actual / +expected):"))))
+        )
+    }
+}
diff --git a/src/matchers/err_matcher.rs b/src/matchers/err_matcher.rs
new file mode 100644
index 0000000..022bc8c
--- /dev/null
+++ b/src/matchers/err_matcher.rs
@@ -0,0 +1,144 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::{fmt::Debug, marker::PhantomData};
+
+/// Matches a `Result` containing `Err` with a value matched by `inner`.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> googletest::Result<()> {
+/// verify_that!(Err::<(), _>("Some error"), err(eq("Some error")))?;  // Passes
+/// #     Ok(())
+/// # }
+/// # fn should_fail_1() -> googletest::Result<()> {
+/// verify_that!(Ok::<_, &str>("A value"), err(eq("A value")))?;   // Fails
+/// #     Ok(())
+/// # }
+/// # fn should_fail_2() -> googletest::Result<()> {
+/// verify_that!(Err::<(), _>("Some error"), err(eq("Some other error")))?;   // Fails
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # should_fail_1().unwrap_err();
+/// # should_fail_2().unwrap_err();
+/// ```
+pub fn err<T: Debug, E: Debug>(
+    inner: impl Matcher<ActualT = E>,
+) -> impl Matcher<ActualT = std::result::Result<T, E>> {
+    ErrMatcher::<T, E, _> { inner, phantom_t: Default::default(), phantom_e: Default::default() }
+}
+
+struct ErrMatcher<T, E, InnerMatcherT> {
+    inner: InnerMatcherT,
+    phantom_t: PhantomData<T>,
+    phantom_e: PhantomData<E>,
+}
+
+impl<T: Debug, E: Debug, InnerMatcherT: Matcher<ActualT = E>> Matcher
+    for ErrMatcher<T, E, InnerMatcherT>
+{
+    type ActualT = std::result::Result<T, E>;
+
+    fn matches(&self, actual: &Self::ActualT) -> MatcherResult {
+        actual.as_ref().err().map(|v| self.inner.matches(v)).unwrap_or(MatcherResult::NoMatch)
+    }
+
+    fn explain_match(&self, actual: &Self::ActualT) -> String {
+        match actual {
+            Err(e) => format!("which is an error {}", self.inner.explain_match(e)),
+            Ok(_) => "which is a success".to_string(),
+        }
+    }
+
+    fn describe(&self, matcher_result: MatcherResult) -> String {
+        match matcher_result {
+            MatcherResult::Match => {
+                format!("is an error which {}", self.inner.describe(MatcherResult::Match))
+            }
+            MatcherResult::NoMatch => {
+                format!(
+                    "is a success or is an error containing a value which {}",
+                    self.inner.describe(MatcherResult::NoMatch)
+                )
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::err;
+    use crate::matcher::{Matcher, MatcherResult};
+    use crate::prelude::*;
+    use indoc::indoc;
+
+    #[test]
+    fn err_matches_result_with_err_value() -> Result<()> {
+        let matcher = err(eq(1));
+        let value: std::result::Result<i32, i32> = Err(1);
+
+        let result = matcher.matches(&value);
+
+        verify_that!(result, eq(MatcherResult::Match))
+    }
+
+    #[test]
+    fn err_does_not_match_result_with_wrong_err_value() -> Result<()> {
+        let matcher = err(eq(1));
+        let value: std::result::Result<i32, i32> = Err(0);
+
+        let result = matcher.matches(&value);
+
+        verify_that!(result, eq(MatcherResult::NoMatch))
+    }
+
+    #[test]
+    fn err_does_not_match_result_with_ok() -> Result<()> {
+        let matcher = err(eq(1));
+        let value: std::result::Result<i32, i32> = Ok(1);
+
+        let result = matcher.matches(&value);
+
+        verify_that!(result, eq(MatcherResult::NoMatch))
+    }
+
+    #[test]
+    fn err_full_error_message() -> Result<()> {
+        let result = verify_that!(Err::<i32, i32>(1), err(eq(2)));
+
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc!(
+                "
+                    Value of: Err::<i32, i32>(1)
+                    Expected: is an error which is equal to 2
+                    Actual: Err(1),
+                      which is an error which isn't equal to 2
+                "
+            ))))
+        )
+    }
+
+    #[test]
+    fn err_describe_matches() -> Result<()> {
+        let matcher = super::ErrMatcher::<i32, i32, _> {
+            inner: eq(1),
+            phantom_t: Default::default(),
+            phantom_e: Default::default(),
+        };
+        verify_that!(matcher.describe(MatcherResult::Match), eq("is an error which is equal to 1"))
+    }
+}
diff --git a/src/matchers/field_matcher.rs b/src/matchers/field_matcher.rs
new file mode 100644
index 0000000..0d1d4fe
--- /dev/null
+++ b/src/matchers/field_matcher.rs
@@ -0,0 +1,201 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// There are no visible documentation elements in this module; the declarative
+// macro is documented at the top level.
+#![doc(hidden)]
+
+/// Matches a structure or enum with a given field which is matched by a given
+/// matcher.
+///
+/// This takes two arguments:
+///
+///  * a specification of the field against which to match, and
+///  * an inner [`Matcher`][crate::matcher::Matcher] to apply to that field.
+///
+/// For example:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// #[derive(Debug)]
+/// struct IntField {
+///   int: i32
+/// }
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(IntField{int: 32}, field!(IntField.int, eq(32)))?;
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// Tuple structs are also supported via the index syntax:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// #[derive(Debug)]
+/// struct IntField(i32);
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(IntField(32), field!(IntField.0, eq(32)))?;
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// Enums are also supported, in which case only the specified variant is
+/// matched:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// #[derive(Debug)]
+/// enum MyEnum {
+///     A(i32),
+///     B,
+/// }
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(MyEnum::A(32), field!(MyEnum::A.0, eq(32)))?; // Passes
+/// #     Ok(())
+/// # }
+/// # fn should_fail() -> Result<()> {
+/// verify_that!(MyEnum::B, field!(MyEnum::A.0, eq(32)))?; // Fails: wrong enum variant
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # should_fail().unwrap_err();
+/// ```
+///
+/// The structure or enum may also be referenced from a separate module:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// mod a_module {
+///     #[derive(Debug)]
+///     pub struct AStruct(pub i32);
+/// }
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(a_module::AStruct(32), field!(a_module::AStruct.0, eq(32)))?;
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// Nested structures are *not supported*, however:
+///
+/// ```compile_fail
+/// # use googletest::prelude::*;
+/// #[derive(Debug)]
+/// struct InnerStruct(i32);
+/// #[derive(Debug)]
+/// struct OuterStruct {
+///     inner: InnerStruct,
+/// }
+/// # fn should_not_compile() -> Result<()> {
+/// verify_that!(value, field!(OuterStruct.inner.0, eq(32)))?; // Does not compile
+/// #     Ok(())
+/// # }
+/// ```
+///
+/// See also the macro [`property`][crate::property] for an analogous mechanism
+/// to extract a datum by invoking a method.
+#[macro_export]
+macro_rules! field {
+    ($($t:tt)*) => { $crate::field_internal!($($t)*) }
+}
+
+// Internal-only macro created so that the macro definition does not appear in
+// generated documentation.
+#[doc(hidden)]
+#[macro_export]
+macro_rules! field_internal {
+    ($($t:ident)::+.$field:tt, $m:expr) => {{
+        use $crate::matchers::field_matcher::internal::field_matcher;
+        field_matcher(
+            |o| {
+                match o {
+                    $($t)::* { $field: value, .. } => Some(value),
+                    // The pattern below is unreachable if the type is a struct (as opposed to an
+                    // enum). Since the macro can't know which it is, we always include it and just
+                    // tell the compiler not to complain.
+                    #[allow(unreachable_patterns)]
+                    _ => None,
+                }
+            },
+            &stringify!($field),
+            $m)
+    }};
+}
+
+/// Functions for use only by the declarative macros in this module.
+///
+/// **For internal use only. API stablility is not guaranteed!**
+#[doc(hidden)]
+pub mod internal {
+    use crate::matcher::{Matcher, MatcherResult};
+    use std::fmt::Debug;
+
+    /// Creates a matcher to verify a specific field of the actual struct using
+    /// the provided inner matcher.
+    ///
+    /// **For internal use only. API stablility is not guaranteed!**
+    #[doc(hidden)]
+    pub fn field_matcher<OuterT: Debug, InnerT: Debug, InnerMatcher: Matcher<ActualT = InnerT>>(
+        field_accessor: fn(&OuterT) -> Option<&InnerT>,
+        field_path: &'static str,
+        inner: InnerMatcher,
+    ) -> impl Matcher<ActualT = OuterT> {
+        FieldMatcher { field_accessor, field_path, inner }
+    }
+
+    struct FieldMatcher<OuterT, InnerT, InnerMatcher> {
+        field_accessor: fn(&OuterT) -> Option<&InnerT>,
+        field_path: &'static str,
+        inner: InnerMatcher,
+    }
+
+    impl<OuterT: Debug, InnerT: Debug, InnerMatcher: Matcher<ActualT = InnerT>> Matcher
+        for FieldMatcher<OuterT, InnerT, InnerMatcher>
+    {
+        type ActualT = OuterT;
+
+        fn matches(&self, actual: &OuterT) -> MatcherResult {
+            if let Some(value) = (self.field_accessor)(actual) {
+                self.inner.matches(value)
+            } else {
+                MatcherResult::NoMatch
+            }
+        }
+
+        fn explain_match(&self, actual: &OuterT) -> String {
+            if let Some(actual) = (self.field_accessor)(actual) {
+                format!(
+                    "which has field `{}`, {}",
+                    self.field_path,
+                    self.inner.explain_match(actual)
+                )
+            } else {
+                let formatted_actual_value = format!("{actual:?}");
+                let without_fields = formatted_actual_value.split('(').next().unwrap_or("");
+                let without_fields = without_fields.split('{').next().unwrap_or("").trim_end();
+                format!("which has the wrong enum variant `{without_fields}`")
+            }
+        }
+
+        fn describe(&self, matcher_result: MatcherResult) -> String {
+            format!(
+                "has field `{}`, which {}",
+                self.field_path,
+                self.inner.describe(matcher_result)
+            )
+        }
+    }
+}
diff --git a/src/matchers/ge_matcher.rs b/src/matchers/ge_matcher.rs
new file mode 100644
index 0000000..33e847e
--- /dev/null
+++ b/src/matchers/ge_matcher.rs
@@ -0,0 +1,218 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::{fmt::Debug, marker::PhantomData};
+
+/// Matches a value greater than or equal to (in the sense of `>=`) `expected`.
+///
+/// The types of `ActualT` of `actual` and `ExpectedT` of `expected` must be
+/// comparable via the `PartialOrd` trait. Namely, `ActualT` must implement
+/// `PartialOrd<ExpectedT>`.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(1, ge(0))?; // Passes
+/// #     Ok(())
+/// # }
+/// # fn should_fail() -> Result<()> {
+/// verify_that!(0, ge(1))?; // Fails
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # should_fail().unwrap_err();
+/// ```
+///
+/// In most cases the params neeed to be the same type or they need to be cast
+/// explicitly. This can be surprising when comparing integer types or
+/// references:
+///
+/// ```compile_fail
+/// # use googletest::prelude::*;
+/// # fn should_not_compile() -> Result<()> {
+/// verify_that!(123u32, ge(0u64))?; // Does not compile
+/// verify_that!(123u32 as u64, ge(0u64))?; // Passes
+/// #     Ok(())
+/// # }
+/// ```
+///
+/// ```compile_fail
+/// # use googletest::prelude::*;
+/// # fn should_not_compile() -> Result<()> {
+/// let actual: &u32 = &2;
+/// let expected: u32 = 0;
+/// verify_that!(actual, ge(expected))?; // Does not compile
+/// #     Ok(())
+/// # }
+/// ```
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// let actual: &u32 = &2;
+/// let expected: u32 = 0;
+/// verify_that!(actual, ge(&expected))?; // Compiles and passes
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// You can find the standard library `PartialOrd` implementation in
+/// <https://doc.rust-lang.org/core/cmp/trait.PartialOrd.html#implementors>
+pub fn ge<ActualT: Debug + PartialOrd<ExpectedT>, ExpectedT: Debug>(
+    expected: ExpectedT,
+) -> impl Matcher<ActualT = ActualT> {
+    GeMatcher::<ActualT, _> { expected, phantom: Default::default() }
+}
+
+struct GeMatcher<ActualT, ExpectedT> {
+    expected: ExpectedT,
+    phantom: PhantomData<ActualT>,
+}
+
+impl<ActualT: Debug + PartialOrd<ExpectedT>, ExpectedT: Debug> Matcher
+    for GeMatcher<ActualT, ExpectedT>
+{
+    type ActualT = ActualT;
+
+    fn matches(&self, actual: &ActualT) -> MatcherResult {
+        (*actual >= self.expected).into()
+    }
+
+    fn describe(&self, matcher_result: MatcherResult) -> String {
+        match matcher_result {
+            MatcherResult::Match => format!("is greater than or equal to {:?}", self.expected),
+            MatcherResult::NoMatch => format!("is less than {:?}", self.expected),
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::ge;
+    use crate::matcher::{Matcher, MatcherResult};
+    use crate::prelude::*;
+    use indoc::indoc;
+    use std::ffi::OsString;
+
+    #[test]
+    fn ge_matches_i32_with_i32() -> Result<()> {
+        let actual: i32 = 0;
+        let expected: i32 = 0;
+        verify_that!(actual, ge(expected))
+    }
+
+    #[test]
+    fn ge_does_not_match_smaller_i32() -> Result<()> {
+        let matcher = ge(10);
+        let result = matcher.matches(&9);
+        verify_that!(result, eq(MatcherResult::NoMatch))
+    }
+
+    #[test]
+    fn ge_matches_bigger_str() -> Result<()> {
+        verify_that!("B", ge("A"))
+    }
+
+    #[test]
+    fn ge_does_not_match_lesser_str() -> Result<()> {
+        let matcher = ge("z");
+        let result = matcher.matches(&"a");
+        verify_that!(result, eq(MatcherResult::NoMatch))
+    }
+
+    #[test]
+    fn ge_mismatch_contains_actual_and_expected() -> Result<()> {
+        let result = verify_that!(591, ge(927));
+
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc!(
+                "
+                Value of: 591
+                Expected: is greater than or equal to 927
+                Actual: 591,
+                  which is less than 927
+                "
+            ))))
+        )
+    }
+
+    // Test `ge` matcher where actual is `&OsString` and expected is `&str`.
+    // Note that stdlib is a little bit inconsistent: `PartialOrd` exists for
+    // `OsString` and `str`, but only in one direction: it's only possible to
+    // compare `OsString` with `str` if `OsString` is on the left side of the
+    // ">=" operator (`impl PartialOrd<str> for OsString`).
+    //
+    // The comparison in the other direction is not defined.
+    //
+    // This means that the test case bellow effectively ensures that
+    // `verify_that(actual, ge(expected))` works if `actual >= expected` works
+    // (regardless whether the `expected >= actual` works`).
+    #[test]
+    fn ge_matches_owned_osstring_reference_with_string_reference() -> Result<()> {
+        let expected = "A";
+        let actual: OsString = "B".to_string().into();
+        verify_that!(&actual, ge(expected))
+    }
+
+    #[test]
+    fn ge_matches_ipv6addr_with_ipaddr() -> Result<()> {
+        use std::net::IpAddr;
+        use std::net::Ipv6Addr;
+        let actual: Ipv6Addr = "2001:4860:4860::8844".parse().unwrap();
+        let expected: IpAddr = "127.0.0.1".parse().unwrap();
+        verify_that!(actual, ge(expected))
+    }
+
+    #[test]
+    fn ge_matches_with_custom_partial_ord() -> Result<()> {
+        /// A custom "number" that is lower than all other numbers. The only
+        /// things we define about this "special" number is `PartialOrd` and
+        /// `PartialEq` against `u32`.
+        #[derive(Debug)]
+        struct VeryLowNumber {}
+
+        impl std::cmp::PartialEq<u32> for VeryLowNumber {
+            fn eq(&self, _other: &u32) -> bool {
+                false
+            }
+        }
+
+        // PartialOrd (required for >) requires PartialEq.
+        impl std::cmp::PartialOrd<u32> for VeryLowNumber {
+            fn partial_cmp(&self, _other: &u32) -> Option<std::cmp::Ordering> {
+                Some(std::cmp::Ordering::Less)
+            }
+        }
+
+        impl std::cmp::PartialEq<VeryLowNumber> for u32 {
+            fn eq(&self, _other: &VeryLowNumber) -> bool {
+                false
+            }
+        }
+
+        impl std::cmp::PartialOrd<VeryLowNumber> for u32 {
+            fn partial_cmp(&self, _other: &VeryLowNumber) -> Option<std::cmp::Ordering> {
+                Some(std::cmp::Ordering::Greater)
+            }
+        }
+
+        let actual: u32 = 42;
+        let expected = VeryLowNumber {};
+
+        verify_that!(actual, ge(expected))
+    }
+}
diff --git a/src/matchers/gt_matcher.rs b/src/matchers/gt_matcher.rs
new file mode 100644
index 0000000..699bf2a
--- /dev/null
+++ b/src/matchers/gt_matcher.rs
@@ -0,0 +1,254 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::{fmt::Debug, marker::PhantomData};
+
+/// Matches a value greater (in the sense of `>`) than `expected`.
+///
+/// The types of `ActualT` of `actual` and `ExpectedT` of `expected` must be
+/// comparable via the `PartialOrd` trait. Namely, `ActualT` must implement
+/// `PartialOrd<ExpectedT>`.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(38, gt(1))?; // Passes
+/// #     Ok(())
+/// # }
+/// # fn should_fail() -> Result<()> {
+/// verify_that!(234, gt(234))?; // Fails
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # should_fail().unwrap_err();
+/// ```
+///
+/// In most cases the params neeed to be the same type or they need to be cast
+/// explicitly. This can be surprising when comparing integer types or
+/// references:
+///
+/// ```compile_fail
+/// # use googletest::prelude::*;
+/// # fn should_not_compile() -> Result<()> {
+/// verify_that!(123u32, gt(0u64))?; // Does not compile
+/// verify_that!(123u32 as u64, gt(0u64))?; // Passes
+/// #     Ok(())
+/// # }
+/// ```
+///
+/// ```compile_fail
+/// # use googletest::prelude::*;
+/// # fn should_not_compile() -> Result<()> {
+/// let actual: &u32 = &2;
+/// let expected: u32 = 1;
+/// verify_that!(actual, gt(expected))?; // Does not compile
+/// #     Ok(())
+/// # }
+/// ```
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// let actual: &u32 = &2;
+/// let expected: u32 = 1;
+/// verify_that!(actual, gt(&expected))?; // Compiles and passes
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// You can find the standard library `PartialOrd` implementation in
+/// <https://doc.rust-lang.org/core/cmp/trait.PartialOrd.html#implementors>
+pub fn gt<ActualT: Debug + PartialOrd<ExpectedT>, ExpectedT: Debug>(
+    expected: ExpectedT,
+) -> impl Matcher<ActualT = ActualT> {
+    GtMatcher::<ActualT, _> { expected, phantom: Default::default() }
+}
+
+struct GtMatcher<ActualT, ExpectedT> {
+    expected: ExpectedT,
+    phantom: PhantomData<ActualT>,
+}
+
+impl<ActualT: Debug + PartialOrd<ExpectedT>, ExpectedT: Debug> Matcher
+    for GtMatcher<ActualT, ExpectedT>
+{
+    type ActualT = ActualT;
+
+    fn matches(&self, actual: &ActualT) -> MatcherResult {
+        (*actual > self.expected).into()
+    }
+
+    fn describe(&self, matcher_result: MatcherResult) -> String {
+        match matcher_result {
+            MatcherResult::Match => format!("is greater than {:?}", self.expected),
+            MatcherResult::NoMatch => format!("is less than or equal to {:?}", self.expected),
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::gt;
+    use crate::matcher::{Matcher, MatcherResult};
+    use crate::prelude::*;
+    use indoc::indoc;
+    use std::ffi::OsString;
+
+    #[test]
+    fn gt_matches_i32_with_i32() -> Result<()> {
+        let actual: i32 = 321;
+        let expected: i32 = 123;
+        verify_that!(actual, gt(expected))
+    }
+
+    #[test]
+    fn gt_does_not_match_equal_i32() -> Result<()> {
+        let matcher = gt(10);
+        let result = matcher.matches(&10);
+        verify_that!(result, eq(MatcherResult::NoMatch))
+    }
+
+    #[test]
+    fn gt_does_not_match_lower_i32() -> Result<()> {
+        let matcher = gt(-50);
+        let result = matcher.matches(&-51);
+        verify_that!(result, eq(MatcherResult::NoMatch))
+    }
+
+    #[test]
+    fn gt_matches_greater_str() -> Result<()> {
+        verify_that!("B", gt("A"))
+    }
+
+    #[test]
+    fn gt_does_not_match_lesser_str() -> Result<()> {
+        let matcher = gt("B");
+        let result = matcher.matches(&"A");
+        verify_that!(result, eq(MatcherResult::NoMatch))
+    }
+
+    #[test]
+    fn gt_mismatch_contains_actual_and_expected() -> Result<()> {
+        let result = verify_that!(481, gt(632));
+
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc!(
+                "
+                Value of: 481
+                Expected: is greater than 632
+                Actual: 481,
+                "
+            ))))
+        )
+    }
+
+    #[test]
+    fn gt_mismatch_combined_with_each() -> Result<()> {
+        let result = verify_that!(vec![19, 23, 11], each(gt(15)));
+
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc!(
+                "
+                Value of: vec![19, 23, 11]
+                Expected: only contains elements that is greater than 15
+                Actual: [19, 23, 11],
+                  whose element #2 is 11, which is less than or equal to 15
+                "
+            ))))
+        )
+    }
+
+    #[test]
+    fn gt_describe_matches() -> Result<()> {
+        verify_that!(gt::<i32, i32>(232).describe(MatcherResult::Match), eq("is greater than 232"))
+    }
+
+    #[test]
+    fn gt_describe_does_not_match() -> Result<()> {
+        verify_that!(
+            gt::<i32, i32>(232).describe(MatcherResult::NoMatch),
+            eq("is less than or equal to 232")
+        )
+    }
+
+    // Test `gt` matcher where actual is `&OsString` and expected is `&str`.
+    // Note that stdlib is a little bit inconsistent: `PartialOrd` exists for
+    // `OsString` and `str`, but only in one direction: it's only possible to
+    // compare `OsString` with `str` if `OsString` is on the left side of the
+    // ">" operator (`impl PartialOrd<str> for OsString`).
+    //
+    // The comparison in the other direction is not defined.
+    //
+    // This means that the test case bellow effectively ensures that
+    // `verify_that(actual, gt(expected))` works if `actual > expected` works
+    // (regardless whether the `expected > actual` works`).
+    #[test]
+    fn gt_matches_owned_osstring_reference_with_string_reference() -> Result<()> {
+        let expected = "A";
+        let actual: OsString = "B".to_string().into();
+        verify_that!(&actual, gt(expected))
+    }
+
+    #[test]
+    fn gt_matches_ipv6addr_with_ipaddr() -> Result<()> {
+        use std::net::IpAddr;
+        use std::net::Ipv6Addr;
+        let actual: Ipv6Addr = "2001:4860:4860::8888".parse().unwrap();
+        let expected: IpAddr = "127.0.0.1".parse().unwrap();
+        verify_that!(actual, gt(expected))
+    }
+
+    #[test]
+    fn gt_matches_with_custom_partial_ord() -> Result<()> {
+        /// A custom "number" that is smaller than all other numbers. The only
+        /// things we define about this "special" number is `PartialOrd` and
+        /// `PartialEq` against `u32`.
+        #[derive(Debug)]
+        struct VeryLowNumber {}
+
+        impl std::cmp::PartialEq<u32> for VeryLowNumber {
+            fn eq(&self, _other: &u32) -> bool {
+                false
+            }
+        }
+
+        // PartialOrd (required for >) requires PartialEq.
+        impl std::cmp::PartialOrd<u32> for VeryLowNumber {
+            fn partial_cmp(&self, _other: &u32) -> Option<std::cmp::Ordering> {
+                Some(std::cmp::Ordering::Less)
+            }
+        }
+
+        impl std::cmp::PartialEq<VeryLowNumber> for u32 {
+            fn eq(&self, _other: &VeryLowNumber) -> bool {
+                false
+            }
+        }
+
+        impl std::cmp::PartialOrd<VeryLowNumber> for u32 {
+            fn partial_cmp(&self, _other: &VeryLowNumber) -> Option<std::cmp::Ordering> {
+                Some(std::cmp::Ordering::Greater)
+            }
+        }
+
+        let actual: u32 = 42;
+        let expected = VeryLowNumber {};
+
+        verify_that!(actual, gt(expected))
+    }
+}
diff --git a/src/matchers/has_entry_matcher.rs b/src/matchers/has_entry_matcher.rs
new file mode 100644
index 0000000..67a44cd
--- /dev/null
+++ b/src/matchers/has_entry_matcher.rs
@@ -0,0 +1,184 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::collections::HashMap;
+use std::fmt::Debug;
+use std::hash::Hash;
+use std::marker::PhantomData;
+
+/// Matches a HashMap containing the given `key` whose value is matched by the
+/// matcher `inner`.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # use std::collections::HashMap;
+/// # fn should_pass() -> Result<()> {
+/// let value = HashMap::from([(0, 1), (1, -1)]);
+/// verify_that!(value, has_entry(0, eq(1)))?;  // Passes
+/// #     Ok(())
+/// # }
+/// # fn should_fail_1() -> Result<()> {
+/// # let value = HashMap::from([(0, 1), (1, -1)]);
+/// verify_that!(value, has_entry(1, gt(0)))?;  // Fails: value not matched
+/// #     Ok(())
+/// # }
+/// # fn should_fail_2() -> Result<()> {
+/// # let value = HashMap::from([(0, 1), (1, -1)]);
+/// verify_that!(value, has_entry(2, eq(0)))?;  // Fails: key not present
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # should_fail_1().unwrap_err();
+/// # should_fail_2().unwrap_err();
+/// ```
+///
+/// Note: One could obtain the same effect by collecting entries into a `Vec`
+/// and using `contains`:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # use std::collections::HashMap;
+/// # fn should_pass() -> Result<()> {
+/// let value = HashMap::from([(0, 1), (1, -1)]);
+/// verify_that!(value.into_iter().collect::<Vec<_>>(), contains(eq((0, 1))))?;
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// However, `has_entry` will offer somewhat better diagnostic messages in the
+/// case of assertion failure. And it avoid the extra allocation hidden in the
+/// code above.
+pub fn has_entry<KeyT: Debug + Eq + Hash, ValueT: Debug, MatcherT: Matcher<ActualT = ValueT>>(
+    key: KeyT,
+    inner: MatcherT,
+) -> impl Matcher<ActualT = HashMap<KeyT, ValueT>> {
+    HasEntryMatcher { key, inner, phantom: Default::default() }
+}
+
+struct HasEntryMatcher<KeyT, ValueT, MatcherT> {
+    key: KeyT,
+    inner: MatcherT,
+    phantom: PhantomData<ValueT>,
+}
+
+impl<KeyT: Debug + Eq + Hash, ValueT: Debug, MatcherT: Matcher<ActualT = ValueT>> Matcher
+    for HasEntryMatcher<KeyT, ValueT, MatcherT>
+{
+    type ActualT = HashMap<KeyT, ValueT>;
+
+    fn matches(&self, actual: &HashMap<KeyT, ValueT>) -> MatcherResult {
+        if let Some(value) = actual.get(&self.key) {
+            self.inner.matches(value)
+        } else {
+            MatcherResult::NoMatch
+        }
+    }
+
+    fn explain_match(&self, actual: &HashMap<KeyT, ValueT>) -> String {
+        if let Some(value) = actual.get(&self.key) {
+            format!(
+                "which contains key {:?}, but is mapped to value {:#?}, {}",
+                self.key,
+                value,
+                self.inner.explain_match(value)
+            )
+        } else {
+            format!("which doesn't contain key {:?}", self.key)
+        }
+    }
+
+    fn describe(&self, matcher_result: MatcherResult) -> String {
+        match matcher_result {
+            MatcherResult::Match => format!(
+                "contains key {:?}, which value {}",
+                self.key,
+                self.inner.describe(MatcherResult::Match)
+            ),
+            MatcherResult::NoMatch => format!(
+                "doesn't contain key {:?} or contains key {:?}, which value {}",
+                self.key,
+                self.key,
+                self.inner.describe(MatcherResult::NoMatch)
+            ),
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::has_entry;
+    use crate::prelude::*;
+    use indoc::indoc;
+    use std::collections::HashMap;
+
+    #[test]
+    fn has_entry_does_not_match_empty_hash_map() -> Result<()> {
+        let value: HashMap<i32, i32> = HashMap::new();
+        verify_that!(value, not(has_entry(0, eq(0))))
+    }
+
+    #[test]
+    fn has_entry_matches_hash_map_with_value() -> Result<()> {
+        let value: HashMap<i32, i32> = HashMap::from([(0, 0)]);
+        verify_that!(value, has_entry(0, eq(0)))
+    }
+
+    #[test]
+    fn has_entry_does_not_match_hash_map_with_wrong_value() -> Result<()> {
+        let value: HashMap<i32, i32> = HashMap::from([(0, 1)]);
+        verify_that!(value, not(has_entry(0, eq(0))))
+    }
+
+    #[test]
+    fn has_entry_does_not_match_hash_map_with_wrong_key() -> Result<()> {
+        let value: HashMap<i32, i32> = HashMap::from([(1, 0)]);
+        verify_that!(value, not(has_entry(0, eq(0))))
+    }
+
+    #[test]
+    fn has_entry_shows_correct_message_when_key_is_not_present() -> Result<()> {
+        let result = verify_that!(HashMap::from([(0, 0)]), has_entry(1, eq(0)));
+
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc!(
+                "
+                Value of: HashMap::from([(0, 0)])
+                Expected: contains key 1, which value is equal to 0
+                Actual: {0: 0},
+                  which doesn't contain key 1
+                "
+            ))))
+        )
+    }
+
+    #[test]
+    fn has_entry_shows_correct_message_when_key_has_non_matching_value() -> Result<()> {
+        let result = verify_that!(HashMap::from([(0, 0)]), has_entry(0, eq(1)));
+
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc!(
+                "
+                Value of: HashMap::from([(0, 0)])
+                Expected: contains key 0, which value is equal to 1
+                Actual: {0: 0},
+                  which contains key 0, but is mapped to value 0, which isn't equal to 1
+                "
+            ))))
+        )
+    }
+}
diff --git a/src/matchers/is_matcher.rs b/src/matchers/is_matcher.rs
new file mode 100644
index 0000000..51fd3b2
--- /dev/null
+++ b/src/matchers/is_matcher.rs
@@ -0,0 +1,65 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#![doc(hidden)]
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::{fmt::Debug, marker::PhantomData};
+
+/// Matches precisely values matched by `inner`.
+///
+/// The returned matcher produces a description prefixed by the string
+/// `description`. This is useful in contexts where the test assertion failure
+/// output must include the additional description.
+pub fn is<'a, ActualT: Debug + 'a, InnerMatcherT: Matcher<ActualT = ActualT> + 'a>(
+    description: &'a str,
+    inner: InnerMatcherT,
+) -> impl Matcher<ActualT = ActualT> + 'a {
+    IsMatcher { description, inner, phantom: Default::default() }
+}
+
+struct IsMatcher<'a, ActualT, InnerMatcherT> {
+    description: &'a str,
+    inner: InnerMatcherT,
+    phantom: PhantomData<ActualT>,
+}
+
+impl<'a, ActualT: Debug, InnerMatcherT: Matcher<ActualT = ActualT>> Matcher
+    for IsMatcher<'a, ActualT, InnerMatcherT>
+{
+    type ActualT = ActualT;
+
+    fn matches(&self, actual: &Self::ActualT) -> MatcherResult {
+        self.inner.matches(actual)
+    }
+
+    fn describe(&self, matcher_result: MatcherResult) -> String {
+        match matcher_result {
+            MatcherResult::Match => format!(
+                "is {} which {}",
+                self.description,
+                self.inner.describe(MatcherResult::Match)
+            ),
+            MatcherResult::NoMatch => format!(
+                "is not {} which {}",
+                self.description,
+                self.inner.describe(MatcherResult::Match)
+            ),
+        }
+    }
+
+    fn explain_match(&self, actual: &Self::ActualT) -> String {
+        self.inner.explain_match(actual)
+    }
+}
diff --git a/src/matchers/is_nan_matcher.rs b/src/matchers/is_nan_matcher.rs
new file mode 100644
index 0000000..3cbe694
--- /dev/null
+++ b/src/matchers/is_nan_matcher.rs
@@ -0,0 +1,62 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use num_traits::float::Float;
+use std::{fmt::Debug, marker::PhantomData};
+
+/// Matches a floating point value which is NaN.
+pub fn is_nan<T: Float + Debug>() -> impl Matcher<ActualT = T> {
+    IsNanMatcher::<T>(Default::default())
+}
+
+struct IsNanMatcher<T>(PhantomData<T>);
+
+impl<T: Float + Debug> Matcher for IsNanMatcher<T> {
+    type ActualT = T;
+
+    fn matches(&self, actual: &T) -> MatcherResult {
+        actual.is_nan().into()
+    }
+
+    fn describe(&self, matcher_result: MatcherResult) -> String {
+        if matcher_result.into() { "is NaN" } else { "isn't NaN" }.to_string()
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::is_nan;
+    use crate::prelude::*;
+
+    #[test]
+    fn matches_f32_nan() -> Result<()> {
+        verify_that!(f32::NAN, is_nan())
+    }
+
+    #[test]
+    fn does_not_match_f32_number() -> Result<()> {
+        verify_that!(0.0f32, not(is_nan()))
+    }
+
+    #[test]
+    fn matches_f64_nan() -> Result<()> {
+        verify_that!(f64::NAN, is_nan())
+    }
+
+    #[test]
+    fn does_not_match_f64_number() -> Result<()> {
+        verify_that!(0.0f64, not(is_nan()))
+    }
+}
diff --git a/src/matchers/le_matcher.rs b/src/matchers/le_matcher.rs
new file mode 100644
index 0000000..6e7a16f
--- /dev/null
+++ b/src/matchers/le_matcher.rs
@@ -0,0 +1,219 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::{fmt::Debug, marker::PhantomData};
+
+/// Matches a value less than or equal to (in the sense of `<=`) `expected`.
+///
+/// The types of `ActualT` of `actual` and `ExpectedT` of `expected` must be
+/// comparable via the `PartialOrd` trait. Namely, `ActualT` must implement
+/// `PartialOrd<ExpectedT>`.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(0, le(0))?; // Passes
+/// #     Ok(())
+/// # }
+/// # fn should_fail() -> Result<()> {
+/// verify_that!(1, le(0))?; // Fails
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # should_fail().unwrap_err();
+/// ```
+///
+/// In most cases the params neeed to be the same type or they need to be cast
+/// explicitly. This can be surprising when comparing integer types or
+/// references:
+///
+/// ```compile_fail
+/// # use googletest::prelude::*;
+/// # fn should_not_compile() -> Result<()> {
+/// verify_that!(1u32, le(2u64))?; // Does not compile
+/// verify_that!(1u32 as u64, le(2u64))?; // Passes
+/// #     Ok(())
+/// # }
+/// ```
+///
+/// ```compile_fail
+/// # use googletest::prelude::*;
+/// # fn should_not_compile() -> Result<()> {
+/// let actual: &u32 = &1;
+/// let expected: u32 = 2;
+/// verify_that!(actual, le(expected))?; // Does not compile
+/// #     Ok(())
+/// # }
+/// ```
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// let actual: &u32 = &1;
+/// let expected: u32 = 2;
+/// verify_that!(actual, le(&expected))?; // Compiles and passes
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// You can find the standard library `PartialOrd` implementation in
+/// <https://doc.rust-lang.org/core/cmp/trait.PartialOrd.html#implementors>
+pub fn le<ActualT: Debug + PartialOrd<ExpectedT>, ExpectedT: Debug>(
+    expected: ExpectedT,
+) -> impl Matcher<ActualT = ActualT> {
+    LeMatcher::<ActualT, _> { expected, phantom: Default::default() }
+}
+
+struct LeMatcher<ActualT, ExpectedT> {
+    expected: ExpectedT,
+    phantom: PhantomData<ActualT>,
+}
+
+impl<ActualT: Debug + PartialOrd<ExpectedT>, ExpectedT: Debug> Matcher
+    for LeMatcher<ActualT, ExpectedT>
+{
+    type ActualT = ActualT;
+
+    fn matches(&self, actual: &ActualT) -> MatcherResult {
+        (*actual <= self.expected).into()
+    }
+
+    fn describe(&self, matcher_result: MatcherResult) -> String {
+        match matcher_result {
+            MatcherResult::Match => format!("is less than or equal to {:?}", self.expected),
+            MatcherResult::NoMatch => format!("is greater than {:?}", self.expected),
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::le;
+    use crate::matcher::{Matcher, MatcherResult};
+    use crate::prelude::*;
+    use indoc::indoc;
+    use std::ffi::OsString;
+
+    #[test]
+    fn le_matches_i32_with_i32() -> Result<()> {
+        let actual: i32 = 0;
+        let expected: i32 = 0;
+        verify_that!(actual, le(expected))
+    }
+
+    #[test]
+    fn le_does_not_match_bigger_i32() -> Result<()> {
+        let matcher = le(0);
+        let result = matcher.matches(&1);
+        verify_that!(result, eq(MatcherResult::NoMatch))
+    }
+
+    #[test]
+    fn le_matches_smaller_str() -> Result<()> {
+        verify_that!("A", le("B"))
+    }
+
+    #[test]
+    fn le_does_not_match_bigger_str() -> Result<()> {
+        let matcher = le("a");
+        let result = matcher.matches(&"z");
+        verify_that!(result, eq(MatcherResult::NoMatch))
+    }
+
+    #[test]
+    fn le_mismatch_contains_actual_and_expected() -> Result<()> {
+        let result = verify_that!(489, le(294));
+        let formatted_message = format!("{}", result.unwrap_err());
+
+        verify_that!(
+            formatted_message.as_str(),
+            contains_substring(indoc!(
+                "
+                Value of: 489
+                Expected: is less than or equal to 294
+                Actual: 489,
+                  which is greater than 294
+                "
+            ))
+        )
+    }
+
+    // Test `le` matcher where actual is `&OsString` and expected is `&str`.
+    // Note that stdlib is a little bit inconsistent: `PartialOrd` exists for
+    // `OsString` and `str`, but only in one direction: it's only possible to
+    // compare `OsString` with `str` if `OsString` is on the left side of the
+    // "<=" operator (`impl PartialOrd<str> for OsString`).
+    //
+    // The comparison in the other direction is not defined.
+    //
+    // This means that the test case bellow effectively ensures that
+    // `verify_that(actual, le(expected))` works if `actual <= expected` works
+    // (regardless whether the `expected <= actual` works`).
+    #[test]
+    fn le_matches_owned_osstring_reference_with_string_reference() -> Result<()> {
+        let expected = "B";
+        let actual: OsString = "A".into();
+        verify_that!(&actual, le(expected))
+    }
+
+    #[test]
+    fn le_matches_ipv6addr_with_ipaddr() -> Result<()> {
+        use std::net::IpAddr;
+        use std::net::Ipv6Addr;
+        let actual: IpAddr = "127.0.0.1".parse().unwrap();
+        let expected: Ipv6Addr = "2001:4860:4860::8844".parse().unwrap();
+        verify_that!(actual, le(expected))
+    }
+
+    #[test]
+    fn le_matches_with_custom_partial_ord() -> Result<()> {
+        /// A custom "number" that is lower than all other numbers. The only
+        /// things we define about this "special" number is `PartialOrd` and
+        /// `PartialEq` against `u32`.
+        #[derive(Debug)]
+        struct VeryLowNumber {}
+
+        impl std::cmp::PartialEq<u32> for VeryLowNumber {
+            fn eq(&self, _other: &u32) -> bool {
+                false
+            }
+        }
+
+        // PartialOrd (required for >) requires PartialEq.
+        impl std::cmp::PartialOrd<u32> for VeryLowNumber {
+            fn partial_cmp(&self, _other: &u32) -> Option<std::cmp::Ordering> {
+                Some(std::cmp::Ordering::Less)
+            }
+        }
+
+        impl std::cmp::PartialEq<VeryLowNumber> for u32 {
+            fn eq(&self, _other: &VeryLowNumber) -> bool {
+                false
+            }
+        }
+
+        impl std::cmp::PartialOrd<VeryLowNumber> for u32 {
+            fn partial_cmp(&self, _other: &VeryLowNumber) -> Option<std::cmp::Ordering> {
+                Some(std::cmp::Ordering::Greater)
+            }
+        }
+
+        let actual = VeryLowNumber {};
+        let expected: u32 = 42;
+
+        verify_that!(actual, le(expected))
+    }
+}
diff --git a/src/matchers/len_matcher.rs b/src/matchers/len_matcher.rs
new file mode 100644
index 0000000..3a31873
--- /dev/null
+++ b/src/matchers/len_matcher.rs
@@ -0,0 +1,211 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use crate::matcher_support::count_elements::count_elements;
+use std::{fmt::Debug, marker::PhantomData};
+
+/// Matches a container whose number of elements matches `expected`.
+///
+/// This matches against a container over which one can iterate. This includes
+/// the standard Rust containers, arrays, and (when dereferenced) slices.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// let array = [1,2,3];
+/// verify_that!(array, len(eq(3)))?;
+/// let vec = vec![1,2,3];
+/// verify_that!(vec, len(eq(3)))?;
+/// let slice = vec.as_slice();
+/// verify_that!(*slice, len(eq(3)))?;
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// The parameter `expected` can be any integer numeric matcher.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// let vec = vec![1,2,3];
+/// verify_that!(vec, len(gt(1)))?;
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+pub fn len<T: Debug + ?Sized, E: Matcher<ActualT = usize>>(expected: E) -> impl Matcher<ActualT = T>
+where
+    for<'a> &'a T: IntoIterator,
+{
+    LenMatcher { expected, phantom: Default::default() }
+}
+
+struct LenMatcher<T: ?Sized, E> {
+    expected: E,
+    phantom: PhantomData<T>,
+}
+
+impl<T: Debug + ?Sized, E: Matcher<ActualT = usize>> Matcher for LenMatcher<T, E>
+where
+    for<'a> &'a T: IntoIterator,
+{
+    type ActualT = T;
+
+    fn matches(&self, actual: &T) -> MatcherResult {
+        self.expected.matches(&count_elements(actual))
+    }
+
+    fn describe(&self, matcher_result: MatcherResult) -> String {
+        match matcher_result {
+            MatcherResult::Match => {
+                format!("has length, which {}", self.expected.describe(MatcherResult::Match))
+            }
+            MatcherResult::NoMatch => {
+                format!("has length, which {}", self.expected.describe(MatcherResult::NoMatch))
+            }
+        }
+    }
+
+    fn explain_match(&self, actual: &T) -> String {
+        let actual_size = count_elements(actual);
+        format!("which has length {}, {}", actual_size, self.expected.explain_match(&actual_size))
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::len;
+    use crate::matcher::{Matcher, MatcherResult};
+    use crate::prelude::*;
+    use indoc::indoc;
+    use std::collections::{
+        BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque,
+    };
+    use std::fmt::Debug;
+    use std::marker::PhantomData;
+
+    #[test]
+    fn len_matcher_match_vec() -> Result<()> {
+        let value = vec![1, 2, 3];
+        verify_that!(value, len(eq(3)))
+    }
+
+    #[test]
+    fn len_matcher_match_array_reference() -> Result<()> {
+        let value = &[1, 2, 3];
+        verify_that!(*value, len(eq(3)))
+    }
+
+    #[test]
+    fn len_matcher_match_slice_of_array() -> Result<()> {
+        let value = &[1, 2, 3];
+        verify_that!(value[0..1], len(eq(1)))
+    }
+
+    #[test]
+    fn len_matcher_match_slice_of_vec() -> Result<()> {
+        let value = vec![1, 2, 3];
+        let slice = value.as_slice();
+        verify_that!(*slice, len(eq(3)))
+    }
+
+    #[test]
+    fn len_matcher_match_sized_slice() -> Result<()> {
+        let value = [1, 2, 3];
+        verify_that!(value, len(eq(3)))
+    }
+
+    #[test]
+    fn len_matcher_match_btreemap() -> Result<()> {
+        let value = BTreeMap::from([(1, 2), (2, 3), (3, 4)]);
+        verify_that!(value, len(eq(3)))
+    }
+
+    #[test]
+    fn len_matcher_match_btreeset() -> Result<()> {
+        let value = BTreeSet::from([1, 2, 3]);
+        verify_that!(value, len(eq(3)))
+    }
+
+    #[test]
+    fn len_matcher_match_binaryheap() -> Result<()> {
+        let value = BinaryHeap::from([1, 2, 3]);
+        verify_that!(value, len(eq(3)))
+    }
+
+    #[test]
+    fn len_matcher_match_hashmap() -> Result<()> {
+        let value = HashMap::from([(1, 2), (2, 3), (3, 4)]);
+        verify_that!(value, len(eq(3)))
+    }
+
+    #[test]
+    fn len_matcher_match_hashset() -> Result<()> {
+        let value = HashSet::from([1, 2, 3]);
+        verify_that!(value, len(eq(3)))
+    }
+
+    #[test]
+    fn len_matcher_match_linkedlist() -> Result<()> {
+        let value = LinkedList::from([1, 2, 3]);
+        verify_that!(value, len(eq(3)))
+    }
+
+    #[test]
+    fn len_matcher_match_vecdeque() -> Result<()> {
+        let value = VecDeque::from([1, 2, 3]);
+        verify_that!(value, len(eq(3)))
+    }
+
+    #[test]
+    fn len_matcher_explain_match() -> Result<()> {
+        struct TestMatcher<T>(PhantomData<T>);
+        impl<T: Debug> Matcher for TestMatcher<T> {
+            type ActualT = T;
+
+            fn matches(&self, _: &T) -> MatcherResult {
+                false.into()
+            }
+
+            fn describe(&self, _: MatcherResult) -> String {
+                "called described".into()
+            }
+
+            fn explain_match(&self, _: &T) -> String {
+                "called explain_match".into()
+            }
+        }
+        verify_that!(
+            len(TestMatcher(Default::default())).explain_match(&[1, 2, 3]),
+            displays_as(eq("which has length 3, called explain_match"))
+        )
+    }
+
+    #[test]
+    fn len_matcher_error_message() -> Result<()> {
+        let result = verify_that!(vec![1, 2, 3, 4], len(eq(3)));
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc!(
+                "
+                Value of: vec![1, 2, 3, 4]
+                Expected: has length, which is equal to 3
+                Actual: [1, 2, 3, 4],
+                  which has length 4, which isn't equal to 3"
+            ))))
+        )
+    }
+}
diff --git a/src/matchers/lt_matcher.rs b/src/matchers/lt_matcher.rs
new file mode 100644
index 0000000..96df00c
--- /dev/null
+++ b/src/matchers/lt_matcher.rs
@@ -0,0 +1,228 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::{fmt::Debug, marker::PhantomData};
+
+/// Matches a value less (in the sense of `<`) than `expected`.
+///
+/// The types of `ActualT` of `actual` and `ExpectedT` of `expected` must be
+/// comparable via the `PartialOrd` trait. Namely, `ActualT` must implement
+/// `PartialOrd<ExpectedT>`.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(1, lt(2))?; // Passes
+/// #     Ok(())
+/// # }
+/// # fn should_fail() -> Result<()> {
+/// verify_that!(2, lt(2))?; // Fails
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # should_fail().unwrap_err();
+/// ```
+///
+/// In most cases the params neeed to be the same type or they need to be cast
+/// explicitly. This can be surprising when comparing integer types or
+/// references:
+///
+/// ```compile_fail
+/// # use googletest::prelude::*;
+/// # fn should_not_compile() -> Result<()> {
+/// verify_that!(123u32, lt(0u64))?; // Does not compile
+/// verify_that!(123u32 as u64, lt(100000000u64))?; // Passes
+/// #     Ok(())
+/// # }
+/// ```
+///
+/// ```compile_fail
+/// # use googletest::prelude::*;
+/// # fn should_not_compile() -> Result<()> {
+/// let actual: &u32 = &2;
+/// let expected: u32 = 70;
+/// verify_that!(actual, lt(expected))?; // Does not compile
+/// #     Ok(())
+/// # }
+/// ```
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// let actual: &u32 = &2;
+/// let expected: u32 = 70;
+/// verify_that!(actual, lt(&expected))?; // Compiles and passes
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// You can find the standard library `PartialOrd` implementation in
+/// <https://doc.rust-lang.org/core/cmp/trait.PartialOrd.html#implementors>
+pub fn lt<ActualT: Debug + PartialOrd<ExpectedT>, ExpectedT: Debug>(
+    expected: ExpectedT,
+) -> impl Matcher<ActualT = ActualT> {
+    LtMatcher::<ActualT, _> { expected, phantom: Default::default() }
+}
+
+struct LtMatcher<ActualT, ExpectedT> {
+    expected: ExpectedT,
+    phantom: PhantomData<ActualT>,
+}
+
+impl<ActualT: Debug + PartialOrd<ExpectedT>, ExpectedT: Debug> Matcher
+    for LtMatcher<ActualT, ExpectedT>
+{
+    type ActualT = ActualT;
+
+    fn matches(&self, actual: &ActualT) -> MatcherResult {
+        (*actual < self.expected).into()
+    }
+
+    fn describe(&self, matcher_result: MatcherResult) -> String {
+        match matcher_result {
+            MatcherResult::Match => format!("is less than {:?}", self.expected),
+            MatcherResult::NoMatch => {
+                format!("is greater than or equal to {:?}", self.expected)
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::lt;
+    use crate::matcher::{Matcher, MatcherResult};
+    use crate::prelude::*;
+    use indoc::indoc;
+    use std::ffi::OsString;
+
+    #[test]
+    fn lt_matches_i32_with_i32() -> Result<()> {
+        let actual: i32 = 10000;
+        let expected: i32 = 20000;
+        verify_that!(actual, lt(expected))
+    }
+
+    #[test]
+    fn lt_does_not_match_equal_i32() -> Result<()> {
+        let matcher = lt(10);
+        let result = matcher.matches(&10);
+        verify_that!(result, eq(MatcherResult::NoMatch))
+    }
+
+    #[test]
+    fn lt_does_not_match_lower_i32() -> Result<()> {
+        let matcher = lt(-50);
+        let result = matcher.matches(&50);
+        verify_that!(result, eq(MatcherResult::NoMatch))
+    }
+
+    #[test]
+    fn lt_matches_lesser_str() -> Result<()> {
+        verify_that!("A", lt("B"))
+    }
+
+    #[test]
+    fn lt_does_not_match_bigger_str() -> Result<()> {
+        let matcher = lt("ab");
+        let result = matcher.matches(&"az");
+        verify_that!(result, eq(MatcherResult::NoMatch))
+    }
+
+    #[test]
+    fn lt_mismatch_contains_actual_and_expected() -> Result<()> {
+        let result = verify_that!(481, lt(45));
+        let formatted_message = format!("{}", result.unwrap_err());
+
+        verify_that!(
+            formatted_message.as_str(),
+            contains_substring(indoc!(
+                "
+                Value of: 481
+                Expected: is less than 45
+                Actual: 481,
+                  which is greater than or equal to 45
+                "
+            ))
+        )
+    }
+
+    // Test `lt` matcher where actual is `&OsString` and expected is `&str`.
+    // Note that stdlib is a little bit inconsistent: `PartialOrd` exists for
+    // `OsString` and `str`, but only in one direction: it's only possible to
+    // compare `OsString` with `str` if `OsString` is on the left side of the
+    // "<" operator (`impl PartialOrd<str> for OsString`).
+    //
+    // The comparison in the other direction is not defined.
+    //
+    // This means that the test case bellow effectively ensures that
+    // `verify_that(actual, lt(expected))` works if `actual < expected` works
+    // (regardless whether the `expected < actual` works`).
+    #[test]
+    fn lt_matches_owned_osstring_reference_with_string_reference() -> Result<()> {
+        let expected = "C";
+        let actual: OsString = "B".to_string().into();
+        verify_that!(&actual, lt(expected))
+    }
+
+    #[test]
+    fn lt_matches_ipv6addr_with_ipaddr() -> Result<()> {
+        use std::net::IpAddr;
+        use std::net::Ipv6Addr;
+        let actual: IpAddr = "127.0.0.1".parse().unwrap();
+        let expected: Ipv6Addr = "2001:4860:4860::8844".parse().unwrap();
+        verify_that!(actual, lt(expected))
+    }
+
+    #[test]
+    fn lt_matches_with_custom_partial_ord() -> Result<()> {
+        /// A custom "number" that is smaller than all other numbers. The only
+        /// things we define about this "special" number is `PartialOrd` and
+        /// `PartialEq` against `u32`.
+        #[derive(Debug)]
+        struct VeryLowNumber {}
+
+        impl std::cmp::PartialEq<u32> for VeryLowNumber {
+            fn eq(&self, _other: &u32) -> bool {
+                false
+            }
+        }
+
+        // PartialOrd (required for >) requires PartialEq.
+        impl std::cmp::PartialOrd<u32> for VeryLowNumber {
+            fn partial_cmp(&self, _other: &u32) -> Option<std::cmp::Ordering> {
+                Some(std::cmp::Ordering::Less)
+            }
+        }
+
+        impl std::cmp::PartialEq<VeryLowNumber> for u32 {
+            fn eq(&self, _other: &VeryLowNumber) -> bool {
+                false
+            }
+        }
+
+        impl std::cmp::PartialOrd<VeryLowNumber> for u32 {
+            fn partial_cmp(&self, _other: &VeryLowNumber) -> Option<std::cmp::Ordering> {
+                Some(std::cmp::Ordering::Greater)
+            }
+        }
+
+        let actual = VeryLowNumber {};
+        let expected: u32 = 42;
+
+        verify_that!(actual, lt(expected))
+    }
+}
diff --git a/src/matchers/matches_pattern.rs b/src/matchers/matches_pattern.rs
new file mode 100644
index 0000000..9c252e5
--- /dev/null
+++ b/src/matchers/matches_pattern.rs
@@ -0,0 +1,599 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// There are no visible documentation elements in this module; the declarative
+// macro is documented at the top level.
+#![doc(hidden)]
+
+/// Matches a value according to a pattern of matchers.
+///
+/// This takes as an argument a specification similar to a struct or enum
+/// initialiser, where each value is a [`Matcher`][crate::matcher::Matcher]
+/// which is applied to the corresponding field.
+///
+/// This can be used to match arbitrary combinations of fields on structures
+/// using arbitrary matchers:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// #[derive(Debug)]
+/// struct MyStruct {
+///     a_field: String,
+///     another_field: String,
+/// }
+///
+/// let my_struct = MyStruct {
+///     a_field: "Something to believe in".into(),
+///     another_field: "Something else".into()
+/// };
+/// verify_that!(my_struct, matches_pattern!(MyStruct {
+///     a_field: starts_with("Something"),
+///     another_field: ends_with("else"),
+/// }))
+/// #     .unwrap();
+/// ```
+///
+/// It is not required to include all named fields in the specification. Omitted
+/// fields have no effect on the output of the matcher.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # #[derive(Debug)]
+/// # struct MyStruct {
+/// #     a_field: String,
+/// #     another_field: String,
+/// # }
+/// #
+/// # let my_struct = MyStruct {
+/// #     a_field: "Something to believe in".into(),
+/// #     another_field: "Something else".into()
+/// # };
+/// verify_that!(my_struct, matches_pattern!(MyStruct {
+///     a_field: starts_with("Something"),
+///     // another_field is missing, so it may be anything.
+/// }))
+/// #     .unwrap();
+/// ```
+///
+/// One can use it recursively to match nested structures:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// #[derive(Debug)]
+/// struct MyStruct {
+///     a_nested_struct: MyInnerStruct,
+/// }
+///
+/// #[derive(Debug)]
+/// struct MyInnerStruct {
+///     a_field: String,
+/// }
+///
+/// let my_struct = MyStruct {
+///     a_nested_struct: MyInnerStruct { a_field: "Something to believe in".into() },
+/// };
+/// verify_that!(my_struct, matches_pattern!(MyStruct {
+///     a_nested_struct: matches_pattern!(MyInnerStruct {
+///         a_field: starts_with("Something"),
+///     }),
+/// }))
+/// #     .unwrap();
+/// ```
+///
+/// One can use the alias [`pat`][crate::pat] to make this less verbose:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # #[derive(Debug)]
+/// # struct MyStruct {
+/// #     a_nested_struct: MyInnerStruct,
+/// # }
+/// #
+/// # #[derive(Debug)]
+/// # struct MyInnerStruct {
+/// #     a_field: String,
+/// # }
+/// #
+/// # let my_struct = MyStruct {
+/// #     a_nested_struct: MyInnerStruct { a_field: "Something to believe in".into() },
+/// # };
+/// verify_that!(my_struct, matches_pattern!(MyStruct {
+///     a_nested_struct: pat!(MyInnerStruct {
+///         a_field: starts_with("Something"),
+///     }),
+/// }))
+/// #     .unwrap();
+/// ```
+///
+/// In addition to fields, one can match on the outputs of methods
+/// ("properties"):
+///
+/// ```
+/// # use googletest::prelude::*;
+/// #[derive(Debug)]
+/// struct MyStruct {
+///     a_field: String,
+/// }
+///
+/// impl MyStruct {
+///     fn get_a_field(&self) -> String { self.a_field.clone() }
+/// }
+///
+/// let my_struct = MyStruct { a_field: "Something to believe in".into() };
+/// verify_that!(my_struct, matches_pattern!(MyStruct {
+///     get_a_field(): starts_with("Something"),
+/// }))
+/// #     .unwrap();
+/// ```
+///
+/// **Important**: The method should be pure function with a deterministic
+/// output and no side effects. In particular, in the event of an assertion
+/// failure, it will be invoked a second time, with the assertion failure output
+/// reflecting the *second* invocation.
+///
+/// These may also include extra parameters you pass in:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # #[derive(Debug)]
+/// # struct MyStruct {
+/// #     a_field: String,
+/// # }
+/// #
+/// impl MyStruct {
+///     fn append_to_a_field(&self, suffix: &str) -> String { self.a_field.clone() + suffix }
+/// }
+///
+/// # let my_struct = MyStruct { a_field: "Something to believe in".into() };
+/// verify_that!(my_struct, matches_pattern!(MyStruct {
+///     append_to_a_field("a suffix"): ends_with("a suffix"),
+/// }))
+/// #     .unwrap();
+/// ```
+///
+/// If the method returns a reference, precede it with the keyword `ref`:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # #[derive(Debug)]
+/// # struct MyStruct {
+/// #     a_field: String,
+/// # }
+/// #
+/// impl MyStruct {
+///     fn get_a_field_ref(&self) -> &String { &self.a_field }
+/// }
+///
+/// # let my_struct = MyStruct { a_field: "Something to believe in".into() };
+/// verify_that!(my_struct, matches_pattern!(MyStruct {
+///     ref get_a_field_ref(): starts_with("Something"),
+/// }))
+/// #    .unwrap();
+/// ```
+///
+/// One can also match tuple structs with up to 10 fields. In this case, all
+/// fields must have matchers:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// #[derive(Debug)]
+/// struct MyTupleStruct(String, String);
+///
+/// let my_struct = MyTupleStruct("Something".into(), "Some other thing".into());
+/// verify_that!(
+///     my_struct,
+///     matches_pattern!(MyTupleStruct(eq("Something"), eq("Some other thing")))
+/// )
+/// #    .unwrap();
+/// ```
+///
+/// One can also match enum values:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// #[derive(Debug)]
+/// enum MyEnum {
+///     A(u32),
+///     B,
+/// }
+///
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(MyEnum::A(123), matches_pattern!(MyEnum::A(eq(123))))?; // Passes
+/// #     Ok(())
+/// # }
+/// # fn should_fail() -> Result<()> {
+/// verify_that!(MyEnum::B, matches_pattern!(MyEnum::A(eq(123))))?; // Fails - wrong enum variant
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # should_fail().unwrap_err();
+/// ```
+///
+/// This macro does not support plain (non-struct) tuples. Use the macro
+/// [`tuple`] for that purpose.
+///
+/// Trailing commas are allowed (but not required) in both ordinary and tuple
+/// structs.
+///
+/// Unfortunately, this matcher does *not* work with methods returning string
+/// slices:
+///
+/// ```compile_fail
+/// # use googletest::prelude::*;
+/// # #[derive(Debug)]
+/// pub struct MyStruct {
+///     a_string: String,
+/// }
+/// impl MyStruct {
+///     pub fn get_a_string(&self) -> &str { &self.a_string }
+/// }
+///
+/// let value = MyStruct { a_string: "A string".into() };
+/// verify_that!(value, matches_pattern!( MyStruct {
+///     get_a_string(): eq("A string"),   // Does not compile
+/// }))
+/// #    .unwrap();
+/// ```
+#[macro_export]
+macro_rules! matches_pattern {
+    ($($t:tt)*) => { $crate::matches_pattern_internal!($($t)*) }
+}
+
+// Internal-only macro created so that the macro definition does not appear in
+// generated documentation.
+#[doc(hidden)]
+#[macro_export]
+macro_rules! matches_pattern_internal {
+    (
+        [$($struct_name:tt)*],
+        { $field_name:ident : $matcher:expr $(,)? }
+    ) => {
+        $crate::matchers::is_matcher::is(
+            stringify!($($struct_name)*),
+            all!(field!($($struct_name)*.$field_name, $matcher))
+        )
+    };
+
+    (
+        [$($struct_name:tt)*],
+        { $property_name:ident($($argument:expr),* $(,)?) : $matcher:expr $(,)? }
+    ) => {
+        $crate::matchers::is_matcher::is(
+            stringify!($($struct_name)*),
+            all!(property!($($struct_name)*.$property_name($($argument),*), $matcher))
+        )
+    };
+
+    (
+        [$($struct_name:tt)*],
+        { ref $property_name:ident($($argument:expr),* $(,)?) : $matcher:expr $(,)? }
+    ) => {
+        $crate::matchers::is_matcher::is(
+            stringify!($($struct_name)*),
+            all!(property!(ref $($struct_name)*.$property_name($($argument),*), $matcher))
+        )
+    };
+
+    (
+        [$($struct_name:tt)*],
+        { $field_name:ident : $matcher:expr, $($rest:tt)* }
+    ) => {
+        $crate::matches_pattern_internal!(
+            all!(field!($($struct_name)*.$field_name, $matcher)),
+            [$($struct_name)*],
+            { $($rest)* }
+        )
+    };
+
+    (
+        [$($struct_name:tt)*],
+        { $property_name:ident($($argument:expr),* $(,)?) : $matcher:expr, $($rest:tt)* }
+    ) => {
+        $crate::matches_pattern_internal!(
+            all!(property!($($struct_name)*.$property_name($($argument),*), $matcher)),
+            [$($struct_name)*],
+            { $($rest)* }
+        )
+    };
+
+    (
+        [$($struct_name:tt)*],
+        { ref $property_name:ident($($argument:expr),* $(,)?) : $matcher:expr, $($rest:tt)* }
+    ) => {
+        $crate::matches_pattern_internal!(
+            all!(property!(ref $($struct_name)*.$property_name($($argument),*), $matcher)),
+            [$($struct_name)*],
+            { $($rest)* }
+        )
+    };
+
+    (
+        all!($($processed:tt)*),
+        [$($struct_name:tt)*],
+        { $field_name:ident : $matcher:expr $(,)? }
+    ) => {
+        $crate::matchers::is_matcher::is(stringify!($($struct_name)*), all!(
+            $($processed)*,
+            field!($($struct_name)*.$field_name, $matcher)
+        ))
+    };
+
+    (
+        all!($($processed:tt)*),
+        [$($struct_name:tt)*],
+        { $property_name:ident($($argument:expr),* $(,)?) : $matcher:expr $(,)? }
+    ) => {
+        $crate::matchers::is_matcher::is(stringify!($($struct_name)*), all!(
+            $($processed)*,
+            property!($($struct_name)*.$property_name($($argument),*), $matcher)
+        ))
+    };
+
+    (
+        all!($($processed:tt)*),
+        [$($struct_name:tt)*],
+        { ref $property_name:ident($($argument:expr),* $(,)?) : $matcher:expr $(,)? }
+    ) => {
+        $crate::matchers::is_matcher::is(stringify!($($struct_name)*), all!(
+            $($processed)*,
+            property!(ref $($struct_name)*.$property_name($($argument),*), $matcher)
+        ))
+    };
+
+    (
+        all!($($processed:tt)*),
+        [$($struct_name:tt)*],
+        { $field_name:ident : $matcher:expr, $($rest:tt)* }
+    ) => {
+        $crate::matches_pattern_internal!(
+            all!(
+                $($processed)*,
+                field!($($struct_name)*.$field_name, $matcher)
+            ),
+            [$($struct_name)*],
+            { $($rest)* }
+        )
+    };
+
+    (
+        all!($($processed:tt)*),
+        [$($struct_name:tt)*],
+        { $property_name:ident($($argument:expr),* $(,)?) : $matcher:expr, $($rest:tt)* }
+    ) => {
+        $crate::matches_pattern_internal!(
+            all!(
+                $($processed)*,
+                property!($($struct_name)*.$property_name($($argument),*), $matcher)
+            ),
+            [$($struct_name)*],
+            { $($rest)* }
+        )
+    };
+
+    (
+        all!($($processed:tt)*),
+        [$($struct_name:tt)*],
+        { ref $property_name:ident($($argument:expr),* $(,)?) : $matcher:expr, $($rest:tt)* }
+    ) => {
+        $crate::matches_pattern_internal!(
+            all!(
+                $($processed)*,
+                property!(ref $($struct_name)*.$property_name($($argument),*), $matcher)
+            ),
+            [$($struct_name)*],
+            { $($rest)* }
+        )
+    };
+
+    (
+        [$($struct_name:tt)*],
+    ) => {
+        $crate::matchers::predicate(|v| matches!(v, $($struct_name)*))
+            .with_description(
+                concat!("is ", stringify!($($struct_name)*)),
+                concat!("is not ", stringify!($($struct_name)*)),
+            )
+    };
+
+    (
+        [$($struct_name:tt)*],
+        ($matcher:expr $(,)?)
+    ) => {
+        $crate::matchers::is_matcher::is(
+            stringify!($($struct_name)*),
+            all!(field!($($struct_name)*.0, $matcher))
+        )
+    };
+
+    (
+        [$($struct_name:tt)*],
+        ($matcher:expr, $($rest:tt)*)
+    ) => {
+        $crate::matches_pattern_internal!(
+            all!(
+                field!($($struct_name)*.0, $matcher)
+            ),
+            [$($struct_name)*],
+            1,
+            ($($rest)*)
+        )
+    };
+
+    (
+        all!($($processed:tt)*),
+        [$($struct_name:tt)*],
+        $field:tt,
+        ($matcher:expr $(,)?)
+    ) => {
+        $crate::matchers::is_matcher::is(stringify!($($struct_name)*), all!(
+            $($processed)*,
+            field!($($struct_name)*.$field, $matcher)
+        ))
+    };
+
+    // We need to repeat this once for every supported field position, unfortunately. There appears
+    // to be no way in declarative macros to compute $field + 1 and have the result evaluated to a
+    // token which can be used as a tuple index.
+    (
+        all!($($processed:tt)*),
+        [$($struct_name:tt)*],
+        1,
+        ($matcher:expr, $($rest:tt)*)
+    ) => {
+        $crate::matches_pattern_internal!(
+            all!(
+                $($processed)*,
+                field!($($struct_name)*.1, $matcher)
+            ),
+            [$($struct_name)*],
+            2,
+            ($($rest)*)
+        )
+    };
+
+    (
+        all!($($processed:tt)*),
+        [$($struct_name:tt)*],
+        2,
+        ($matcher:expr, $($rest:tt)*)
+    ) => {
+        $crate::matches_pattern_internal!(
+            all!(
+                $($processed)*,
+                field!($($struct_name)*.2, $matcher)
+            ),
+            [$($struct_name)*],
+            3,
+            ($($rest)*)
+        )
+    };
+
+    (
+        all!($($processed:tt)*),
+        [$($struct_name:tt)*],
+        3,
+        ($matcher:expr, $($rest:tt)*)
+    ) => {
+        $crate::matches_pattern_internal!(
+            all!(
+                $($processed)*,
+                field!($($struct_name)*.3, $matcher)
+            ),
+            [$($struct_name)*],
+            4,
+            ($($rest)*)
+        )
+    };
+
+    (
+        all!($($processed:tt)*),
+        [$($struct_name:tt)*],
+        4,
+        ($matcher:expr, $($rest:tt)*)
+    ) => {
+        $crate::matches_pattern_internal!(
+            all!(
+                $($processed)*,
+                field!($($struct_name)*.4, $matcher)
+            ),
+            [$($struct_name)*],
+            5,
+            ($($rest)*)
+        )
+    };
+
+    (
+        all!($($processed:tt)*),
+        [$($struct_name:tt)*],
+        5,
+        ($matcher:expr, $($rest:tt)*)
+    ) => {
+        $crate::matches_pattern_internal!(
+            all!(
+                $($processed)*,
+                field!($($struct_name)*.5, $matcher)
+            ),
+            [$($struct_name)*],
+            6,
+            ($($rest)*)
+        )
+    };
+
+    (
+        all!($($processed:tt)*),
+        [$($struct_name:tt)*],
+        6,
+        ($matcher:expr, $($rest:tt)*)
+    ) => {
+        $crate::matches_pattern_internal!(
+            all!(
+                $($processed)*,
+                field!($($struct_name)*.6, $matcher)
+            ),
+            [$($struct_name)*],
+            7,
+            ($($rest)*)
+        )
+    };
+
+    (
+        all!($($processed:tt)*),
+        [$($struct_name:tt)*],
+        7,
+        ($matcher:expr, $($rest:tt)*)
+    ) => {
+        $crate::matches_pattern_internal!(
+            all!(
+                $($processed)*,
+                field!($($struct_name)*.7, $matcher)
+            ),
+            [$($struct_name)*],
+            8,
+            ($($rest)*)
+        )
+    };
+
+    (
+        all!($($processed:tt)*),
+        [$($struct_name:tt)*],
+        8,
+        ($matcher:expr, $($rest:tt)*)
+    ) => {
+        $crate::matches_pattern_internal!(
+            all!(
+                $($processed)*,
+                field!($($struct_name)*.8, $matcher)
+            ),
+            [$($struct_name)*],
+            9,
+            ($($rest)*)
+        )
+    };
+
+    ([$($struct_name:tt)*], $first:tt $($rest:tt)*) => {
+        $crate::matches_pattern_internal!([$($struct_name)* $first], $($rest)*)
+    };
+
+    ($first:tt $($rest:tt)*) => {{
+        #[allow(unused)]
+        use $crate::{all, field, property};
+        $crate::matches_pattern_internal!([$first], $($rest)*)
+    }};
+}
+
+/// An alias for [`matches_pattern`].
+#[macro_export]
+macro_rules! pat {
+    ($($t:tt)*) => { $crate::matches_pattern_internal!($($t)*) }
+}
diff --git a/src/matchers/matches_regex_matcher.rs b/src/matchers/matches_regex_matcher.rs
new file mode 100644
index 0000000..d0001e2
--- /dev/null
+++ b/src/matchers/matches_regex_matcher.rs
@@ -0,0 +1,210 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use regex::Regex;
+use std::fmt::Debug;
+use std::marker::PhantomData;
+use std::ops::Deref;
+
+/// Matches a string the entirety of which which matches the given regular
+/// expression.
+///
+/// This is similar to [`contains_regex`][crate::matchers::contains_regex],
+/// except that the match must cover the whole string and not a substring.
+///
+/// Both the actual value and the expected regular expression may be either a
+/// `String` or a string reference.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass_1() -> Result<()> {
+/// verify_that!("Some value", matches_regex("S.*e"))?;  // Passes
+/// #     Ok(())
+/// # }
+/// # fn should_fail_1() -> Result<()> {
+/// verify_that!("Another value", matches_regex("Some"))?;   // Fails
+/// #     Ok(())
+/// # }
+/// # fn should_fail_2() -> Result<()> {
+/// verify_that!("Some value", matches_regex("Some"))?;   // Fails
+/// #     Ok(())
+/// # }
+/// # fn should_pass_2() -> Result<()> {
+/// verify_that!("Some value".to_string(), matches_regex(".*v.*e"))?;   // Passes
+/// verify_that!("Some value", matches_regex(".*v.*e".to_string()))?;   // Passes
+/// #     Ok(())
+/// # }
+/// # should_pass_1().unwrap();
+/// # should_fail_1().unwrap_err();
+/// # should_fail_2().unwrap_err();
+/// # should_pass_2().unwrap();
+/// ```
+///
+/// Panics if the given `pattern` is not a syntactically valid regular
+/// expression.
+// N.B. This returns the concrete type rather than an impl Matcher so that it
+// can act simultaneously as a Matcher<str> and a Matcher<String>. Otherwise the
+// compiler treats it as a Matcher<str> only and the code
+//   verify_that!("Some value".to_string(), matches_regex(".*value"))?;
+// doesn't compile.
+pub fn matches_regex<ActualT: ?Sized, PatternT: Deref<Target = str>>(
+    pattern: PatternT,
+) -> MatchesRegexMatcher<ActualT, PatternT> {
+    let adjusted_pattern = format!("^{}$", pattern.deref());
+    let regex = Regex::new(adjusted_pattern.as_str()).unwrap();
+    MatchesRegexMatcher {
+        regex,
+        pattern,
+        _adjusted_pattern: adjusted_pattern,
+        phantom: Default::default(),
+    }
+}
+
+/// A matcher matching a string-like type matching a given regular expression.
+///
+/// Intended only to be used from the function [`matches_regex`] only.
+/// Should not be referenced by code outside this library.
+pub struct MatchesRegexMatcher<ActualT: ?Sized, PatternT: Deref<Target = str>> {
+    regex: Regex,
+    pattern: PatternT,
+    _adjusted_pattern: String,
+    phantom: PhantomData<ActualT>,
+}
+
+impl<PatternT, ActualT> Matcher for MatchesRegexMatcher<ActualT, PatternT>
+where
+    PatternT: Deref<Target = str>,
+    ActualT: AsRef<str> + Debug + ?Sized,
+{
+    type ActualT = ActualT;
+
+    fn matches(&self, actual: &Self::ActualT) -> MatcherResult {
+        self.regex.is_match(actual.as_ref()).into()
+    }
+
+    fn describe(&self, matcher_result: MatcherResult) -> String {
+        match matcher_result {
+            MatcherResult::Match => {
+                format!("matches the regular expression {:#?}", self.pattern.deref())
+            }
+            MatcherResult::NoMatch => {
+                format!("doesn't match the regular expression {:#?}", self.pattern.deref())
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::{matches_regex, MatchesRegexMatcher};
+    use crate::matcher::{Matcher, MatcherResult};
+    use crate::prelude::*;
+
+    #[test]
+    fn matches_regex_matches_string_reference_with_pattern() -> Result<()> {
+        let matcher = matches_regex("S.*e");
+
+        let result = matcher.matches("Some value");
+
+        verify_that!(result, eq(MatcherResult::Match))
+    }
+
+    #[test]
+    fn matches_regex_does_not_match_string_without_pattern() -> Result<()> {
+        let matcher = matches_regex("Another");
+
+        let result = matcher.matches("Some value");
+
+        verify_that!(result, eq(MatcherResult::NoMatch))
+    }
+
+    #[test]
+    fn matches_regex_does_not_match_string_only_beginning_of_which_matches() -> Result<()> {
+        let matcher = matches_regex("Some");
+
+        let result = matcher.matches("Some value");
+
+        verify_that!(result, eq(MatcherResult::NoMatch))
+    }
+
+    #[test]
+    fn matches_regex_does_not_match_string_only_end_of_which_matches() -> Result<()> {
+        let matcher = matches_regex("value");
+
+        let result = matcher.matches("Some value");
+
+        verify_that!(result, eq(MatcherResult::NoMatch))
+    }
+
+    #[test]
+    fn matches_regex_matches_owned_string_with_pattern() -> Result<()> {
+        let matcher = matches_regex(".*value");
+
+        let result = matcher.matches(&"Some value".to_string());
+
+        verify_that!(result, eq(MatcherResult::Match))
+    }
+
+    #[test]
+    fn matches_regex_matches_string_when_regex_has_beginning_of_string_marker() -> Result<()> {
+        let matcher = matches_regex("^Some value");
+
+        let result = matcher.matches("Some value");
+
+        verify_that!(result, eq(MatcherResult::Match))
+    }
+
+    #[test]
+    fn matches_regex_matches_string_when_regex_has_end_of_string_marker() -> Result<()> {
+        let matcher = matches_regex("Some value$");
+
+        let result = matcher.matches("Some value");
+
+        verify_that!(result, eq(MatcherResult::Match))
+    }
+
+    #[test]
+    fn matches_regex_matches_string_when_regex_has_both_end_markers() -> Result<()> {
+        let matcher = matches_regex("^Some value$");
+
+        let result = matcher.matches("Some value");
+
+        verify_that!(result, eq(MatcherResult::Match))
+    }
+
+    #[test]
+    fn matches_regex_matches_string_reference_with_owned_string() -> Result<()> {
+        let matcher = matches_regex(".*value".to_string());
+
+        let result = matcher.matches("Some value");
+
+        verify_that!(result, eq(MatcherResult::Match))
+    }
+
+    #[test]
+    fn verify_that_works_with_owned_string() -> Result<()> {
+        verify_that!("Some value".to_string(), matches_regex(".*value"))
+    }
+
+    #[test]
+    fn matches_regex_displays_quoted_debug_of_pattern() -> Result<()> {
+        let matcher: MatchesRegexMatcher<&str, _> = matches_regex("\n");
+
+        verify_that!(
+            Matcher::describe(&matcher, MatcherResult::Match),
+            eq("matches the regular expression \"\\n\"")
+        )
+    }
+}
diff --git a/src/matchers/mod.rs b/src/matchers/mod.rs
new file mode 100644
index 0000000..f8aef10
--- /dev/null
+++ b/src/matchers/mod.rs
@@ -0,0 +1,89 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! All built-in matchers of this crate are in submodules of this module.
+
+pub mod all_matcher;
+pub mod any_matcher;
+mod anything_matcher;
+mod char_count_matcher;
+pub mod conjunction_matcher;
+mod container_eq_matcher;
+mod contains_matcher;
+mod contains_regex_matcher;
+pub mod disjunction_matcher;
+mod display_matcher;
+mod each_matcher;
+pub mod elements_are_matcher;
+mod empty_matcher;
+mod eq_deref_of_matcher;
+mod eq_matcher;
+mod err_matcher;
+pub mod field_matcher;
+mod ge_matcher;
+mod gt_matcher;
+mod has_entry_matcher;
+pub mod is_matcher;
+mod is_nan_matcher;
+mod le_matcher;
+mod len_matcher;
+mod lt_matcher;
+mod matches_pattern;
+mod matches_regex_matcher;
+mod near_matcher;
+mod none_matcher;
+mod not_matcher;
+mod ok_matcher;
+mod points_to_matcher;
+pub mod pointwise_matcher;
+mod predicate_matcher;
+pub mod property_matcher;
+mod some_matcher;
+mod str_matcher;
+mod subset_of_matcher;
+mod superset_of_matcher;
+mod tuple_matcher;
+pub mod unordered_elements_are_matcher;
+
+pub use anything_matcher::anything;
+pub use char_count_matcher::char_count;
+pub use container_eq_matcher::container_eq;
+pub use contains_matcher::{contains, ContainsMatcher};
+pub use contains_regex_matcher::contains_regex;
+pub use display_matcher::displays_as;
+pub use each_matcher::each;
+pub use empty_matcher::empty;
+pub use eq_deref_of_matcher::eq_deref_of;
+pub use eq_matcher::{eq, EqMatcher};
+pub use err_matcher::err;
+pub use ge_matcher::ge;
+pub use gt_matcher::gt;
+pub use has_entry_matcher::has_entry;
+pub use is_nan_matcher::is_nan;
+pub use le_matcher::le;
+pub use len_matcher::len;
+pub use lt_matcher::lt;
+pub use matches_regex_matcher::matches_regex;
+pub use near_matcher::{approx_eq, near, NearMatcher};
+pub use none_matcher::none;
+pub use not_matcher::not;
+pub use ok_matcher::ok;
+pub use points_to_matcher::points_to;
+pub use predicate_matcher::{predicate, PredicateMatcher};
+pub use some_matcher::some;
+pub use str_matcher::{
+    contains_substring, ends_with, starts_with, StrMatcher, StrMatcherConfigurator,
+};
+pub use subset_of_matcher::subset_of;
+pub use superset_of_matcher::superset_of;
diff --git a/src/matchers/near_matcher.rs b/src/matchers/near_matcher.rs
new file mode 100644
index 0000000..484939c
--- /dev/null
+++ b/src/matchers/near_matcher.rs
@@ -0,0 +1,347 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use num_traits::{Float, FloatConst};
+use std::fmt::Debug;
+
+/// Matches a value equal within `max_abs_error` of `expected`.
+///
+/// The type `T` of the actual, `expected`, and `max_abs_error` values must
+/// implement [`Float`].
+///
+/// The values `expected` and `max_abs_error` may not be NaN. The value
+/// `max_abs_error` must be non-negative. The matcher panics on construction
+/// otherwise.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass_1() -> Result<()> {
+/// verify_that!(1.0, near(1.0, 0.1))?; // Passes
+/// verify_that!(1.01, near(1.0, 0.1))?; // Passes
+/// verify_that!(1.25, near(1.0, 0.25))?; // Passes
+/// verify_that!(0.75, near(1.0, 0.25))?; // Passes
+/// #     Ok(())
+/// # }
+/// # fn should_fail_1() -> Result<()> {
+/// verify_that!(1.101, near(1.0, 0.1))?; // Fails
+/// #     Ok(())
+/// # }
+/// # fn should_fail_2() -> Result<()> {
+/// verify_that!(0.899, near(1.0, 0.1))?; // Fails
+/// #     Ok(())
+/// # }
+/// # fn should_pass_2() -> Result<()> {
+/// verify_that!(100.25, near(100.0, 0.25))?; // Passes
+/// #     Ok(())
+/// # }
+/// # should_pass_1().unwrap();
+/// # should_fail_1().unwrap_err();
+/// # should_fail_2().unwrap_err();
+/// # should_pass_2().unwrap();
+/// ```
+///
+/// The default behaviour for special values is consistent with the IEEE
+/// floating point standard. Thus infinity is infinitely far away from any
+/// floating point value:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_fail_1() -> Result<()> {
+/// verify_that!(f64::INFINITY, near(0.0, f64::MAX))?; // Fails
+/// #     Ok(())
+/// # }
+/// # fn should_fail_2() -> Result<()> {
+/// verify_that!(0.0, near(f64::INFINITY, f64::MAX))?; // Fails
+/// #     Ok(())
+/// # }
+/// # fn should_fail_3() -> Result<()> {
+/// verify_that!(f64::INFINITY, near(f64::INFINITY, f64::MAX))?; // Fails
+/// #     Ok(())
+/// # }
+/// # should_fail_1().unwrap_err();
+/// # should_fail_2().unwrap_err();
+/// # should_fail_3().unwrap_err();
+/// ```
+///
+/// Similarly, by default, `NaN` is infinitely far away from any value:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_fail_1() -> Result<()> {
+/// verify_that!(f64::NAN, near(0.0, f64::MAX))?; // Fails
+/// #     Ok(())
+/// # }
+/// # fn should_fail_2() -> Result<()> {
+/// verify_that!(0.0, near(f64::NAN, f64::MAX))?; // Fails
+/// #     Ok(())
+/// # }
+/// # fn should_fail_3() -> Result<()> {
+/// verify_that!(f64::NAN, near(f64::NAN, f64::MAX))?; // Fails
+/// #     Ok(())
+/// # }
+/// # should_fail_1().unwrap_err();
+/// # should_fail_2().unwrap_err();
+/// # should_fail_3().unwrap_err();
+/// ```
+///
+/// To treat two `NaN` values as equal, use the method
+/// [`NearMatcher::nans_are_equal`].
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(f64::NAN, near(f64::NAN, f64::MAX).nans_are_equal())?; // Passes
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+pub fn near<T: Debug + Float + Copy>(expected: T, max_abs_error: T) -> NearMatcher<T> {
+    if max_abs_error.is_nan() {
+        panic!("max_abs_error must not be NaN");
+    }
+    if max_abs_error < T::zero() {
+        panic!("max_abs_error must be non-negative");
+    }
+    NearMatcher { expected, max_abs_error, nans_are_equal: false }
+}
+
+/// Matches a value approximately equal to `expected`.
+///
+/// This automatically computes a tolerance from the magnitude of `expected` and
+/// matches any actual value within this tolerance of the expected value. The
+/// tolerance is chosen to account for the inaccuracies in most ordinary
+/// floating point calculations.
+///
+/// Otherwise this works analogously to [`near`]; see its documentation for
+/// further notes.
+pub fn approx_eq<T: Debug + Float + FloatConst + Copy>(expected: T) -> NearMatcher<T> {
+    // The FloatConst trait doesn't offer 2 as a constant but does offer 1.
+    let five_bits_of_mantissa = (T::one() + T::one()).powi(5);
+    let abs_tolerance = five_bits_of_mantissa * T::epsilon();
+    let max_abs_error = T::max(expected.abs() * abs_tolerance, abs_tolerance);
+    NearMatcher { expected, max_abs_error, nans_are_equal: false }
+}
+
+/// A matcher which matches floating-point numbers approximately equal to its
+/// expected value.
+pub struct NearMatcher<T: Debug> {
+    expected: T,
+    max_abs_error: T,
+    nans_are_equal: bool,
+}
+
+impl<T: Debug> NearMatcher<T> {
+    /// Configures this instance to treat two NaNs as equal.
+    ///
+    /// This behaviour differs from the IEEE standad for floating point which
+    /// treats two NaNs as infinitely far apart.
+    pub fn nans_are_equal(mut self) -> Self {
+        self.nans_are_equal = true;
+        self
+    }
+
+    /// Configures this instance to treat two NaNs as not equal.
+    ///
+    /// This behaviour complies with the IEEE standad for floating point. It is
+    /// the default behaviour for this matcher, so invoking this method is
+    /// usually redunant.
+    pub fn nans_are_not_equal(mut self) -> Self {
+        self.nans_are_equal = false;
+        self
+    }
+}
+
+impl<T: Debug + Float> Matcher for NearMatcher<T> {
+    type ActualT = T;
+
+    fn matches(&self, actual: &T) -> MatcherResult {
+        if self.nans_are_equal && self.expected.is_nan() && actual.is_nan() {
+            return MatcherResult::Match;
+        }
+
+        let delta = *actual - self.expected;
+        if delta >= -self.max_abs_error && delta <= self.max_abs_error {
+            MatcherResult::Match
+        } else {
+            MatcherResult::NoMatch
+        }
+    }
+
+    fn describe(&self, matcher_result: MatcherResult) -> String {
+        match matcher_result {
+            MatcherResult::Match => {
+                format!("is within {:?} of {:?}", self.max_abs_error, self.expected)
+            }
+            MatcherResult::NoMatch => {
+                format!("isn't within {:?} of {:?}", self.max_abs_error, self.expected)
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::{approx_eq, near};
+    use crate::matcher::{Matcher, MatcherResult};
+    use crate::prelude::*;
+
+    #[test]
+    fn matches_value_inside_range() -> Result<()> {
+        let matcher = near(1.0f64, 0.1f64);
+
+        let result = matcher.matches(&1.0f64);
+
+        verify_that!(result, eq(MatcherResult::Match))
+    }
+
+    #[test]
+    fn matches_value_at_low_end_of_range() -> Result<()> {
+        let matcher = near(1.0f64, 0.1f64);
+
+        let result = matcher.matches(&0.9f64);
+
+        verify_that!(result, eq(MatcherResult::Match))
+    }
+
+    #[test]
+    fn matches_value_at_high_end_of_range() -> Result<()> {
+        let matcher = near(1.0f64, 0.25f64);
+
+        let result = matcher.matches(&1.25f64);
+
+        verify_that!(result, eq(MatcherResult::Match))
+    }
+
+    #[test]
+    fn does_not_match_value_below_low_end_of_range() -> Result<()> {
+        let matcher = near(1.0f64, 0.1f64);
+
+        let result = matcher.matches(&0.899999f64);
+
+        verify_that!(result, eq(MatcherResult::NoMatch))
+    }
+
+    #[test]
+    fn does_not_match_value_above_high_end_of_range() -> Result<()> {
+        let matcher = near(1.0f64, 0.1f64);
+
+        let result = matcher.matches(&1.100001f64);
+
+        verify_that!(result, eq(MatcherResult::NoMatch))
+    }
+
+    #[test]
+    fn nan_is_not_near_a_number() -> Result<()> {
+        let matcher = near(0.0f64, f64::MAX);
+
+        let result = matcher.matches(&f64::NAN);
+
+        verify_that!(result, eq(MatcherResult::NoMatch))
+    }
+
+    #[test]
+    fn nan_is_not_near_nan_by_default() -> Result<()> {
+        verify_that!(f64::NAN, not(near(f64::NAN, f64::MAX)))
+    }
+
+    #[test]
+    fn nan_is_not_near_nan_when_explicitly_configured() -> Result<()> {
+        verify_that!(f64::NAN, not(near(f64::NAN, f64::MAX).nans_are_not_equal()))
+    }
+
+    #[test]
+    fn nan_is_near_nan_if_nans_are_equal() -> Result<()> {
+        verify_that!(f64::NAN, near(f64::NAN, f64::MAX).nans_are_equal())
+    }
+
+    #[test]
+    fn nan_is_not_near_number_when_nans_are_equal() -> Result<()> {
+        verify_that!(f64::NAN, not(near(0.0, f64::MAX).nans_are_equal()))
+    }
+
+    #[test]
+    fn number_is_not_near_nan_when_nans_are_equal() -> Result<()> {
+        verify_that!(0.0, not(near(f64::NAN, f64::MAX).nans_are_equal()))
+    }
+
+    #[test]
+    fn inf_is_not_near_inf() -> Result<()> {
+        let matcher = near(f64::INFINITY, f64::MAX);
+
+        let result = matcher.matches(&f64::INFINITY);
+
+        verify_that!(result, eq(MatcherResult::NoMatch))
+    }
+
+    #[test]
+    fn inf_is_not_near_a_number() -> Result<()> {
+        let matcher = near(f64::INFINITY, f64::MAX);
+
+        let result = matcher.matches(&f64::MIN);
+
+        verify_that!(result, eq(MatcherResult::NoMatch))
+    }
+
+    #[test]
+    fn any_two_numbers_are_within_inf_of_each_other() -> Result<()> {
+        let matcher = near(f64::MIN, f64::INFINITY);
+
+        let result = matcher.matches(&f64::MAX);
+
+        verify_that!(result, eq(MatcherResult::Match))
+    }
+
+    #[::core::prelude::v1::test]
+    #[should_panic]
+    fn panics_if_max_abs_error_is_nan() {
+        near(0.0, f64::NAN);
+    }
+
+    #[::core::prelude::v1::test]
+    #[should_panic]
+    fn panics_if_tolerance_is_negative() {
+        near(0.0, -1.0);
+    }
+
+    #[test]
+    fn approx_eq_matches_equal_number() -> Result<()> {
+        verify_that!(1.0f64, approx_eq(1.0f64))
+    }
+
+    #[test]
+    fn approx_eq_matches_really_close_f64_number() -> Result<()> {
+        verify_that!(1.0f64, approx_eq(1.0 + 16.0 * f64::EPSILON))
+    }
+
+    #[test]
+    fn approx_eq_matches_really_close_f64_number_to_large_number() -> Result<()> {
+        verify_that!(1000f64, approx_eq(1000.0 + 16000.0 * f64::EPSILON))
+    }
+
+    #[test]
+    fn approx_eq_matches_really_close_f64_number_to_zero() -> Result<()> {
+        verify_that!(16.0 * f64::EPSILON, approx_eq(0.0))
+    }
+
+    #[test]
+    fn approx_eq_matches_really_close_f32_number() -> Result<()> {
+        verify_that!(1.0f32, approx_eq(1.0 + 16.0 * f32::EPSILON))
+    }
+
+    #[test]
+    fn approx_eq_does_not_match_distant_number() -> Result<()> {
+        verify_that!(0.0f64, not(approx_eq(1.0f64)))
+    }
+}
diff --git a/src/matchers/none_matcher.rs b/src/matchers/none_matcher.rs
new file mode 100644
index 0000000..e48d549
--- /dev/null
+++ b/src/matchers/none_matcher.rs
@@ -0,0 +1,80 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::fmt::Debug;
+use std::marker::PhantomData;
+
+/// Matches an `Option` containing `None`.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(None::<()>, none())?;   // Passes
+/// #     Ok(())
+/// # }
+/// # fn should_fail() -> Result<()> {
+/// verify_that!(Some("Some value"), none())?;  // Fails
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # should_fail().unwrap_err();
+/// ```
+pub fn none<T: Debug>() -> impl Matcher<ActualT = Option<T>> {
+    NoneMatcher::<T> { phantom: Default::default() }
+}
+
+struct NoneMatcher<T> {
+    phantom: PhantomData<T>,
+}
+
+impl<T: Debug> Matcher for NoneMatcher<T> {
+    type ActualT = Option<T>;
+
+    fn matches(&self, actual: &Option<T>) -> MatcherResult {
+        (actual.is_none()).into()
+    }
+
+    fn describe(&self, matcher_result: MatcherResult) -> String {
+        match matcher_result {
+            MatcherResult::Match => "is none".to_string(),
+            MatcherResult::NoMatch => "is some(_)".to_string(),
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::none;
+    use crate::matcher::{Matcher, MatcherResult};
+    use crate::prelude::*;
+
+    #[test]
+    fn none_matches_option_with_none() -> Result<()> {
+        let matcher = none::<i32>();
+
+        let result = matcher.matches(&None);
+
+        verify_that!(result, eq(MatcherResult::Match))
+    }
+
+    #[test]
+    fn none_does_not_match_option_with_value() -> Result<()> {
+        let matcher = none();
+
+        let result = matcher.matches(&Some(0));
+
+        verify_that!(result, eq(MatcherResult::NoMatch))
+    }
+}
diff --git a/src/matchers/not_matcher.rs b/src/matchers/not_matcher.rs
new file mode 100644
index 0000000..1dff791
--- /dev/null
+++ b/src/matchers/not_matcher.rs
@@ -0,0 +1,106 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::{fmt::Debug, marker::PhantomData};
+
+/// Matches the actual value exactly when the inner matcher does _not_ match.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(0, not(eq(1)))?; // Passes
+/// #     Ok(())
+/// # }
+/// # fn should_fail() -> Result<()> {
+/// verify_that!(0, not(eq(0)))?; // Fails
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # should_fail().unwrap_err();
+/// ```
+pub fn not<T: Debug, InnerMatcherT: Matcher<ActualT = T>>(
+    inner: InnerMatcherT,
+) -> impl Matcher<ActualT = T> {
+    NotMatcher::<T, _> { inner, phantom: Default::default() }
+}
+
+struct NotMatcher<T, InnerMatcherT> {
+    inner: InnerMatcherT,
+    phantom: PhantomData<T>,
+}
+
+impl<T: Debug, InnerMatcherT: Matcher<ActualT = T>> Matcher for NotMatcher<T, InnerMatcherT> {
+    type ActualT = T;
+
+    fn matches(&self, actual: &T) -> MatcherResult {
+        match self.inner.matches(actual) {
+            MatcherResult::Match => MatcherResult::NoMatch,
+            MatcherResult::NoMatch => MatcherResult::Match,
+        }
+    }
+
+    fn explain_match(&self, actual: &T) -> String {
+        self.inner.explain_match(actual)
+    }
+
+    fn describe(&self, matcher_result: MatcherResult) -> String {
+        self.inner.describe(if matcher_result.into() {
+            MatcherResult::NoMatch
+        } else {
+            MatcherResult::Match
+        })
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::not;
+    use crate::matcher::{Matcher, MatcherResult};
+    use crate::prelude::*;
+    use indoc::indoc;
+
+    #[test]
+    fn matches_when_inner_matcher_does_not_match() -> Result<()> {
+        let matcher = not(eq(1));
+
+        let result = matcher.matches(&0);
+
+        verify_that!(result, eq(MatcherResult::Match))
+    }
+
+    #[test]
+    fn does_not_match_when_inner_matcher_matches() -> Result<()> {
+        let matcher = not(eq(1));
+
+        let result = matcher.matches(&1);
+
+        verify_that!(result, eq(MatcherResult::NoMatch))
+    }
+
+    #[test]
+    fn match_explanation_references_actual_value() -> Result<()> {
+        let result = verify_that!([1], not(container_eq([1])));
+
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc!(
+                "
+                Actual: [1],
+                  which contains all the elements
+                "
+            ))))
+        )
+    }
+}
diff --git a/src/matchers/ok_matcher.rs b/src/matchers/ok_matcher.rs
new file mode 100644
index 0000000..5c6fa51
--- /dev/null
+++ b/src/matchers/ok_matcher.rs
@@ -0,0 +1,150 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::{fmt::Debug, marker::PhantomData};
+
+/// Matches a `Result` containing `Ok` with a value matched by `inner`.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> googletest::Result<()> {
+/// verify_that!(Ok::<_, ()>("Some value"), ok(eq("Some value")))?;  // Passes
+/// #     Ok(())
+/// # }
+/// # fn should_fail_1() -> googletest::Result<()> {
+/// verify_that!(Err::<&str, _>("An error"), ok(eq("An error")))?;   // Fails
+/// #     Ok(())
+/// # }
+/// # fn should_fail_2() -> googletest::Result<()> {
+/// verify_that!(Ok::<_, ()>("Some value"), ok(eq("Some other value")))?;   // Fails
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # should_fail_1().unwrap_err();
+/// # should_fail_2().unwrap_err();
+/// ```
+pub fn ok<T: Debug, E: Debug>(
+    inner: impl Matcher<ActualT = T>,
+) -> impl Matcher<ActualT = std::result::Result<T, E>> {
+    OkMatcher::<T, E, _> { inner, phantom_t: Default::default(), phantom_e: Default::default() }
+}
+
+struct OkMatcher<T, E, InnerMatcherT> {
+    inner: InnerMatcherT,
+    phantom_t: PhantomData<T>,
+    phantom_e: PhantomData<E>,
+}
+
+impl<T: Debug, E: Debug, InnerMatcherT: Matcher<ActualT = T>> Matcher
+    for OkMatcher<T, E, InnerMatcherT>
+{
+    type ActualT = std::result::Result<T, E>;
+
+    fn matches(&self, actual: &Self::ActualT) -> MatcherResult {
+        actual.as_ref().map(|v| self.inner.matches(v)).unwrap_or(MatcherResult::NoMatch)
+    }
+
+    fn explain_match(&self, actual: &Self::ActualT) -> String {
+        match actual {
+            Ok(o) => format!("which is a success {}", self.inner.explain_match(o)),
+            Err(_) => "which is an error".to_string(),
+        }
+    }
+
+    fn describe(&self, matcher_result: MatcherResult) -> String {
+        match matcher_result {
+            MatcherResult::Match => {
+                format!(
+                    "is a success containing a value, which {}",
+                    self.inner.describe(MatcherResult::Match)
+                )
+            }
+            MatcherResult::NoMatch => {
+                format!(
+                    "is an error or a success containing a value, which {}",
+                    self.inner.describe(MatcherResult::NoMatch)
+                )
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::ok;
+    use crate::matcher::{Matcher, MatcherResult};
+    use crate::prelude::*;
+    use indoc::indoc;
+
+    #[test]
+    fn ok_matches_result_with_value() -> Result<()> {
+        let matcher = ok(eq(1));
+        let value: std::result::Result<i32, i32> = Ok(1);
+
+        let result = matcher.matches(&value);
+
+        verify_that!(result, eq(MatcherResult::Match))
+    }
+
+    #[test]
+    fn ok_does_not_match_result_with_wrong_value() -> Result<()> {
+        let matcher = ok(eq(1));
+        let value: std::result::Result<i32, i32> = Ok(0);
+
+        let result = matcher.matches(&value);
+
+        verify_that!(result, eq(MatcherResult::NoMatch))
+    }
+
+    #[test]
+    fn ok_does_not_match_result_with_err() -> Result<()> {
+        let matcher = ok(eq(1));
+        let value: std::result::Result<i32, i32> = Err(1);
+
+        let result = matcher.matches(&value);
+
+        verify_that!(result, eq(MatcherResult::NoMatch))
+    }
+
+    #[test]
+    fn ok_full_error_message() -> Result<()> {
+        let result = verify_that!(Ok::<i32, i32>(1), ok(eq(2)));
+
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc!(
+                "
+                    Value of: Ok::<i32, i32>(1)
+                    Expected: is a success containing a value, which is equal to 2
+                    Actual: Ok(1),
+                      which is a success which isn't equal to 2
+                "
+            ))))
+        )
+    }
+
+    #[test]
+    fn ok_describe_matches() -> Result<()> {
+        let matcher = super::OkMatcher::<i32, i32, _> {
+            inner: eq(1),
+            phantom_t: Default::default(),
+            phantom_e: Default::default(),
+        };
+        verify_that!(
+            matcher.describe(MatcherResult::Match),
+            eq("is a success containing a value, which is equal to 1")
+        )
+    }
+}
diff --git a/src/matchers/points_to_matcher.rs b/src/matchers/points_to_matcher.rs
new file mode 100644
index 0000000..08c7343
--- /dev/null
+++ b/src/matchers/points_to_matcher.rs
@@ -0,0 +1,107 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::fmt::Debug;
+use std::marker::PhantomData;
+use std::ops::Deref;
+
+/// Matches a (smart) pointer pointing to a value matched by the [`Matcher`]
+/// `expected`.
+///
+/// This allows easily matching smart pointers such as `Box`, `Rc`, and `Arc`.
+/// For example:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(Box::new(123), points_to(eq(123)))?;
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+pub fn points_to<ExpectedT, MatcherT, ActualT>(
+    expected: MatcherT,
+) -> impl Matcher<ActualT = ActualT>
+where
+    ExpectedT: Debug,
+    MatcherT: Matcher<ActualT = ExpectedT>,
+    ActualT: Deref<Target = ExpectedT> + Debug + ?Sized,
+{
+    PointsToMatcher { expected, phantom: Default::default() }
+}
+
+struct PointsToMatcher<ActualT: ?Sized, MatcherT> {
+    expected: MatcherT,
+    phantom: PhantomData<ActualT>,
+}
+
+impl<ExpectedT, MatcherT, ActualT> Matcher for PointsToMatcher<ActualT, MatcherT>
+where
+    ExpectedT: Debug,
+    MatcherT: Matcher<ActualT = ExpectedT>,
+    ActualT: Deref<Target = ExpectedT> + Debug + ?Sized,
+{
+    type ActualT = ActualT;
+
+    fn matches(&self, actual: &ActualT) -> MatcherResult {
+        self.expected.matches(actual.deref())
+    }
+
+    fn explain_match(&self, actual: &ActualT) -> String {
+        self.expected.explain_match(actual.deref())
+    }
+
+    fn describe(&self, matcher_result: MatcherResult) -> String {
+        self.expected.describe(matcher_result)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::points_to;
+    use crate::prelude::*;
+    use indoc::indoc;
+    use std::rc::Rc;
+
+    #[test]
+    fn points_to_matches_box_of_int_with_int() -> Result<()> {
+        verify_that!(Box::new(123), points_to(eq(123)))
+    }
+
+    #[test]
+    fn points_to_matches_rc_of_int_with_int() -> Result<()> {
+        verify_that!(Rc::new(123), points_to(eq(123)))
+    }
+
+    #[test]
+    fn points_to_matches_box_of_owned_string_with_string_reference() -> Result<()> {
+        verify_that!(Rc::new("A string".to_string()), points_to(eq("A string")))
+    }
+
+    #[test]
+    fn match_explanation_references_actual_value() -> Result<()> {
+        let result = verify_that!(&vec![1], points_to(container_eq([])));
+
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc!(
+                "
+                    Actual: [1],
+                      which contains the unexpected element 1
+                "
+            ))))
+        )
+    }
+}
diff --git a/src/matchers/pointwise_matcher.rs b/src/matchers/pointwise_matcher.rs
new file mode 100644
index 0000000..5ee0f22
--- /dev/null
+++ b/src/matchers/pointwise_matcher.rs
@@ -0,0 +1,236 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// There are no visible documentation elements in this module; the declarative
+// macro is documented at the top level.
+#![doc(hidden)]
+
+/// Generates a matcher which matches a container each of whose elements match
+/// the given matcher name applied respectively to each element of the given
+/// container.
+///
+/// For example, the following matches a container of integers each of which
+/// does not exceed the given upper bounds:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// let value = vec![1, 2, 3];
+/// verify_that!(value, pointwise!(le, [1, 3, 3]))?; // Passes
+/// verify_that!(value, pointwise!(le, vec![1, 3, 3]))?; // Passes
+/// #     Ok(())
+/// # }
+/// # fn should_fail() -> Result<()> {
+/// #     let value = vec![1, 2, 3];
+/// verify_that!(value, pointwise!(le, [1, 1, 3]))?; // Fails
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # should_fail().unwrap_err();
+/// ```
+///
+/// One can also use a closure which returns a matcher:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// let value = vec![1.00001, 2.000001, 3.00001];
+/// verify_that!(value, pointwise!(|v| near(v, 0.001), [1.0, 2.0, 3.0]))?;
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// One can pass up to three containers to supply arguments to the function
+/// creating the matcher:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// let value = vec![1.00001, 2.000001, 3.00001];
+/// verify_that!(value, pointwise!(|v, t| near(v, t), [1.0, 2.0, 3.0], [0.001, 0.0001, 0.01]))?;
+/// verify_that!(value, pointwise!(near, [1.0, 2.0, 3.0], [0.001, 0.0001, 0.01]))?; // Same as above
+/// verify_that!(
+///     value,
+///     pointwise!(
+///         |v, t, u| near(v, t * u),
+///         [1.0, 2.0, 3.0],
+///         [0.001, 0.0001, 0.01],
+///         [0.5, 0.5, 1.0]
+///     )
+/// )?;
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// When using `pointwise!` with multiple containers, the caller must ensure
+/// that all of the containers have the same size. This matcher does not check
+/// whether the sizes match.
+///
+/// The actual value must be a container implementing [`IntoIterator`]. This
+/// includes standard containers, slices (when dereferenced) and arrays.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// let value = vec![1, 2, 3];
+/// verify_that!(*value.as_slice(), pointwise!(le, [1, 3, 3]))?; // Passes
+/// verify_that!([1, 2, 3], pointwise!(le, [1, 3, 3]))?; // Passes
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// This matcher does not support matching directly against an [`Iterator`]. To
+/// match against an iterator, use [`Iterator::collect`] to build a [`Vec`]
+/// first.
+///
+/// The second argument can be any value implementing `IntoIterator`, such as a
+/// `Vec` or an array. The container does not have to have the same type as the
+/// actual value, but the value type must be the same.
+///
+/// **Note for users of the [`Pointwise`] matcher in C++ GoogleTest:**
+///
+/// This macro differs from `Pointwise` in that the first parameter is not a
+/// matcher which matches a pair but rather the name of a function of one
+/// argument whose output is a matcher. This means that one can use standard
+/// matchers like `eq`, `le`, and so on with `pointwise!` but certain C++ tests
+/// using `Pointwise` will require some extra work to port.
+///
+/// [`IntoIterator`]: std::iter::IntoIterator
+/// [`Iterator`]: std::iter::Iterator
+/// [`Iterator::collect`]: std::iter::Iterator::collect
+/// [`Pointwise`]: https://google.github.io/googletest/reference/matchers.html#container-matchers
+/// [`Vec`]: std::vec::Vec
+#[macro_export]
+macro_rules! pointwise {
+    ($matcher:expr, $container:expr) => {{
+        use $crate::matchers::pointwise_matcher::internal::PointwiseMatcher;
+        PointwiseMatcher::new($container.into_iter().map($matcher).collect())
+    }};
+
+    ($matcher:expr, $left_container:expr, $right_container:expr) => {{
+        use $crate::matchers::pointwise_matcher::internal::PointwiseMatcher;
+        PointwiseMatcher::new(
+            $left_container
+                .into_iter()
+                .zip($right_container.into_iter())
+                .map(|(l, r)| $matcher(l, r))
+                .collect(),
+        )
+    }};
+
+    ($matcher:expr, $left_container:expr, $middle_container:expr, $right_container:expr) => {{
+        use $crate::matchers::pointwise_matcher::internal::PointwiseMatcher;
+        PointwiseMatcher::new(
+            $left_container
+                .into_iter()
+                .zip($right_container.into_iter().zip($middle_container.into_iter()))
+                .map(|(l, (m, r))| $matcher(l, m, r))
+                .collect(),
+        )
+    }};
+}
+
+/// Module for use only by the procedural macros in this module.
+///
+/// **For internal use only. API stablility is not guaranteed!**
+#[doc(hidden)]
+pub mod internal {
+    use crate::matcher::{Matcher, MatcherResult};
+    use crate::matcher_support::description::Description;
+    use crate::matcher_support::zipped_iterator::zip;
+    use std::{fmt::Debug, marker::PhantomData};
+
+    /// This struct is meant to be used only through the `pointwise` macro.
+    ///
+    /// **For internal use only. API stablility is not guaranteed!**
+    #[doc(hidden)]
+    pub struct PointwiseMatcher<ContainerT: ?Sized, MatcherT> {
+        matchers: Vec<MatcherT>,
+        phantom: PhantomData<ContainerT>,
+    }
+
+    impl<ContainerT: ?Sized, MatcherT> PointwiseMatcher<ContainerT, MatcherT> {
+        pub fn new(matchers: Vec<MatcherT>) -> Self {
+            Self { matchers, phantom: Default::default() }
+        }
+    }
+
+    impl<T: Debug, MatcherT: Matcher<ActualT = T>, ContainerT: ?Sized + Debug> Matcher
+        for PointwiseMatcher<ContainerT, MatcherT>
+    where
+        for<'b> &'b ContainerT: IntoIterator<Item = &'b T>,
+    {
+        type ActualT = ContainerT;
+
+        fn matches(&self, actual: &ContainerT) -> MatcherResult {
+            let mut zipped_iterator = zip(actual.into_iter(), self.matchers.iter());
+            for (element, matcher) in zipped_iterator.by_ref() {
+                if matcher.matches(element).is_no_match() {
+                    return MatcherResult::NoMatch;
+                }
+            }
+            if zipped_iterator.has_size_mismatch() {
+                MatcherResult::NoMatch
+            } else {
+                MatcherResult::Match
+            }
+        }
+
+        fn explain_match(&self, actual: &ContainerT) -> String {
+            // TODO(b/260819741) This code duplicates elements_are_matcher.rs. Consider
+            // extract as a separate library. (or implement pointwise! with
+            // elements_are)
+            let actual_iterator = actual.into_iter();
+            let mut zipped_iterator = zip(actual_iterator, self.matchers.iter());
+            let mut mismatches = Vec::new();
+            for (idx, (a, e)) in zipped_iterator.by_ref().enumerate() {
+                if e.matches(a).is_no_match() {
+                    mismatches.push(format!("element #{idx} is {a:?}, {}", e.explain_match(a)));
+                }
+            }
+            if mismatches.is_empty() {
+                if !zipped_iterator.has_size_mismatch() {
+                    "which matches all elements".to_string()
+                } else {
+                    format!(
+                        "which has size {} (expected {})",
+                        zipped_iterator.left_size(),
+                        self.matchers.len()
+                    )
+                }
+            } else if mismatches.len() == 1 {
+                format!("where {}", mismatches[0])
+            } else {
+                let mismatches = mismatches.into_iter().collect::<Description>();
+                format!("where:\n{}", mismatches.bullet_list().indent())
+            }
+        }
+
+        fn describe(&self, matcher_result: MatcherResult) -> String {
+            format!(
+                "{} elements satisfying respectively:\n{}",
+                if matcher_result.into() { "has" } else { "doesn't have" },
+                self.matchers
+                    .iter()
+                    .map(|m| m.describe(MatcherResult::Match))
+                    .collect::<Description>()
+                    .enumerate()
+                    .indent()
+            )
+        }
+    }
+}
diff --git a/src/matchers/predicate_matcher.rs b/src/matchers/predicate_matcher.rs
new file mode 100644
index 0000000..fabd6c3
--- /dev/null
+++ b/src/matchers/predicate_matcher.rs
@@ -0,0 +1,228 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::{fmt::Debug, marker::PhantomData};
+
+/// Creates a matcher based on the predicate provided.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(3, predicate(|x: &i32| x % 2 == 1))?;  // Passes
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// The predicate should take the subject type by reference and return a
+/// boolean.
+///
+/// Note: even if the Rust compiler should be able to infer the type of
+/// the closure argument, it is likely that it won't.
+/// See <https://github.com/rust-lang/rust/issues/12679> for update on this issue.
+/// This is easily fixed by explicitly declaring the type of the argument
+pub fn predicate<T: Debug + ?Sized, P>(
+    predicate: P,
+) -> PredicateMatcher<T, P, NoDescription, NoDescription>
+where
+    for<'a> P: Fn(&'a T) -> bool,
+{
+    PredicateMatcher {
+        predicate,
+        positive_description: NoDescription,
+        negative_description: NoDescription,
+        phantom: Default::default(),
+    }
+}
+
+impl<T, P> PredicateMatcher<T, P, NoDescription, NoDescription> {
+    /// Configures this instance to provide a more meaningful description.
+    ///
+    /// For example, to make sure the error message is more useful
+    ///
+    /// ```
+    /// # use googletest::matchers::{predicate, PredicateMatcher};
+    /// # let _ =
+    /// predicate(|x: &i32| x % 2 == 1)
+    ///     .with_description("is odd", "is even")
+    /// # ;
+    /// ```
+    ///
+    /// This is optional as it only provides value when the test fails.
+    ///
+    /// Description can be passed by `&str`, `String` or `Fn() -> Into<String>`.
+    pub fn with_description<D1: PredicateDescription, D2: PredicateDescription>(
+        self,
+        positive_description: D1,
+        negative_description: D2,
+    ) -> PredicateMatcher<T, P, D1, D2> {
+        PredicateMatcher {
+            predicate: self.predicate,
+            positive_description,
+            negative_description,
+            phantom: Default::default(),
+        }
+    }
+}
+
+/// A matcher which applies `predicate` on the value.
+///
+/// See [`predicate`].
+pub struct PredicateMatcher<T: ?Sized, P, D1, D2> {
+    predicate: P,
+    positive_description: D1,
+    negative_description: D2,
+    phantom: PhantomData<T>,
+}
+
+/// A trait to allow [`PredicateMatcher::with_description`] to accept multiple
+/// types.
+///
+/// See [`PredicateMatcher::with_description`]
+pub trait PredicateDescription {
+    fn to_description(&self) -> String;
+}
+
+impl PredicateDescription for &str {
+    fn to_description(&self) -> String {
+        self.to_string()
+    }
+}
+
+impl PredicateDescription for String {
+    fn to_description(&self) -> String {
+        self.clone()
+    }
+}
+
+impl<T, S> PredicateDescription for T
+where
+    T: Fn() -> S,
+    S: Into<String>,
+{
+    fn to_description(&self) -> String {
+        self().into()
+    }
+}
+
+// Sentinel type to tag a MatcherBuilder as without a description.
+#[doc(hidden)]
+pub struct NoDescription;
+
+impl<T: Debug, P> Matcher for PredicateMatcher<T, P, NoDescription, NoDescription>
+where
+    for<'a> P: Fn(&'a T) -> bool,
+{
+    type ActualT = T;
+
+    fn matches(&self, actual: &T) -> MatcherResult {
+        (self.predicate)(actual).into()
+    }
+
+    fn describe(&self, result: MatcherResult) -> String {
+        match result {
+            MatcherResult::Match => "matches".to_string(),
+            MatcherResult::NoMatch => "does not match".to_string(),
+        }
+    }
+}
+
+impl<T: Debug, P, D1: PredicateDescription, D2: PredicateDescription> Matcher
+    for PredicateMatcher<T, P, D1, D2>
+where
+    for<'a> P: Fn(&'a T) -> bool,
+{
+    type ActualT = T;
+
+    fn matches(&self, actual: &T) -> MatcherResult {
+        (self.predicate)(actual).into()
+    }
+
+    fn describe(&self, result: MatcherResult) -> String {
+        match result {
+            MatcherResult::Match => self.positive_description.to_description(),
+            MatcherResult::NoMatch => self.negative_description.to_description(),
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::predicate;
+    use crate::matcher::Matcher;
+    use crate::prelude::*;
+
+    // Simple matcher with a description
+    fn is_odd() -> impl Matcher<ActualT = i32> {
+        predicate(|x| x % 2 == 1).with_description("is odd", "is even")
+    }
+
+    #[test]
+    fn predicate_matcher_odd() -> Result<()> {
+        verify_that!(1, is_odd())
+    }
+
+    #[test]
+    fn predicate_matcher_odd_explain_match_matches() -> Result<()> {
+        verify_that!(is_odd().explain_match(&1), displays_as(eq("which is odd")))
+    }
+
+    #[test]
+    fn predicate_matcher_odd_explain_match_does_not_match() -> Result<()> {
+        verify_that!(is_odd().explain_match(&2), displays_as(eq("which is even")))
+    }
+
+    // Simple Matcher without description
+    fn is_even() -> impl Matcher<ActualT = i32> {
+        predicate(|x| x % 2 == 0)
+    }
+
+    #[test]
+    fn predicate_matcher_even() -> Result<()> {
+        verify_that!(2, is_even())
+    }
+
+    #[test]
+    fn predicate_matcher_even_explain_match_matches() -> Result<()> {
+        verify_that!(is_even().explain_match(&2), displays_as(eq("which matches")))
+    }
+
+    #[test]
+    fn predicate_matcher_even_explain_match_does_not_match() -> Result<()> {
+        verify_that!(is_even().explain_match(&1), displays_as(eq("which does not match")))
+    }
+
+    #[test]
+    fn predicate_matcher_generator_lambda() -> Result<()> {
+        let is_divisible_by = |quotient| {
+            predicate(move |x: &i32| x % quotient == 0).with_description(
+                move || format!("is divisible by {quotient}"),
+                move || format!("is not divisible by {quotient}"),
+            )
+        };
+        verify_that!(49, is_divisible_by(7))
+    }
+
+    #[test]
+    fn predicate_matcher_inline() -> Result<()> {
+        verify_that!(2048, predicate(|x: &i32| x.count_ones() == 1))
+    }
+
+    #[test]
+    fn predicate_matcher_function_pointer() -> Result<()> {
+        use std::time::Duration;
+        verify_that!(Duration::new(0, 0), predicate(Duration::is_zero))
+    }
+}
diff --git a/src/matchers/property_matcher.rs b/src/matchers/property_matcher.rs
new file mode 100644
index 0000000..d69ba1d
--- /dev/null
+++ b/src/matchers/property_matcher.rs
@@ -0,0 +1,237 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// There are no visible documentation elements in this module; the declarative
+// macro is documented at the top level.
+#![doc(hidden)]
+
+/// Matches an object which, upon calling the given method on it with the given
+/// arguments, produces a value matched by the given inner matcher.
+///
+/// This is particularly useful as a nested matcher when the desired
+/// property cannot be accessed through a field and must instead be
+/// extracted through a method call. For example:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// #[derive(Debug)]
+/// pub struct MyStruct {
+///     a_field: u32,
+/// }
+///
+/// impl MyStruct {
+///     pub fn get_a_field(&self) -> u32 { self.a_field }
+/// }
+///
+/// let value = vec![MyStruct { a_field: 100 }];
+/// verify_that!(value, contains(property!(MyStruct.get_a_field(), eq(100))))
+/// #    .unwrap();
+/// ```
+///
+/// **Important**: The method should be pure function with a deterministic
+/// output and no side effects. In particular, in the event of an assertion
+/// failure, it will be invoked a second time, with the assertion failure output
+/// reflecting the *second* invocation.
+///
+/// If the method returns a *reference*, then it must be preceded by the keyword
+/// `ref`:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # #[derive(Debug)]
+/// # pub struct MyStruct {
+/// #     a_field: u32,
+/// # }
+/// impl MyStruct {
+///     pub fn get_a_field(&self) -> &u32 { &self.a_field }
+/// }
+///
+/// # let value = vec![MyStruct { a_field: 100 }];
+/// verify_that!(value, contains(property!(ref MyStruct.get_a_field(), eq(100))))
+/// #    .unwrap();
+/// ```
+///
+/// The method may also take additional arguments:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # #[derive(Debug)]
+/// # pub struct MyStruct {
+/// #     a_field: u32,
+/// # }
+/// impl MyStruct {
+///     pub fn add_to_a_field(&self, a: u32) -> u32 { self.a_field + a }
+/// }
+///
+/// # let value = vec![MyStruct { a_field: 100 }];
+/// verify_that!(value, contains(property!(MyStruct.add_to_a_field(50), eq(150))))
+/// #    .unwrap();
+/// ```
+///
+/// Unfortunately, this matcher does *not* work with methods returning string
+/// slices:
+///
+/// ```compile_fail
+/// # use googletest::prelude::*;
+/// #[derive(Debug)]
+/// pub struct MyStruct {
+///     a_string: String,
+/// }
+/// impl MyStruct {
+///     pub fn get_a_string(&self) -> &str { &self.a_string }
+/// }
+///
+/// let value = MyStruct { a_string: "A string".into() };
+/// verify_that!(value, property!(ref MyStruct.get_a_string(), eq("A string"))) // Does not compile
+/// #    .unwrap();
+/// ```
+///
+/// This macro is analogous to [`field`][crate::field], except that it extracts
+/// the datum to be matched from the given object by invoking a method rather
+/// than accessing a field.
+///
+/// The list of arguments may optionally have a trailing comma.
+#[macro_export]
+macro_rules! property {
+    ($($t:tt)*) => { $crate::property_internal!($($t)*) }
+}
+
+// Internal-only macro created so that the macro definition does not appear in
+// generated documentation.
+#[doc(hidden)]
+#[macro_export]
+macro_rules! property_internal {
+    ($($t:ident)::+.$method:tt($($argument:tt),* $(,)?), $m:expr) => {{
+         use $crate::matchers::property_matcher::internal::property_matcher;
+        property_matcher(
+            |o: &$($t)::+| o.$method($($argument),*),
+            &stringify!($method($($argument),*)),
+            $m)
+    }};
+
+    (ref $($t:ident)::+.$method:tt($($argument:tt),* $(,)?), $m:expr) => {{
+        use $crate::matchers::property_matcher::internal::property_ref_matcher;
+        property_ref_matcher(
+            |o: &$($t)::+| o.$method($($argument),*),
+            &stringify!($method($($argument),*)),
+            $m)
+    }};
+}
+
+/// Items for use only by the declarative macros in this module.
+///
+/// **For internal use only. API stablility is not guaranteed!**
+#[doc(hidden)]
+pub mod internal {
+    use crate::matcher::{Matcher, MatcherResult};
+    use std::{fmt::Debug, marker::PhantomData};
+
+    /// **For internal use only. API stablility is not guaranteed!**
+    #[doc(hidden)]
+    pub fn property_matcher<OuterT: Debug, InnerT: Debug, MatcherT: Matcher<ActualT = InnerT>>(
+        extractor: impl Fn(&OuterT) -> InnerT,
+        property_desc: &'static str,
+        inner: MatcherT,
+    ) -> impl Matcher<ActualT = OuterT> {
+        PropertyMatcher { extractor, property_desc, inner, phantom: Default::default() }
+    }
+
+    struct PropertyMatcher<OuterT, ExtractorT, MatcherT> {
+        extractor: ExtractorT,
+        property_desc: &'static str,
+        inner: MatcherT,
+        phantom: PhantomData<OuterT>,
+    }
+
+    impl<InnerT, OuterT, ExtractorT, MatcherT> Matcher for PropertyMatcher<OuterT, ExtractorT, MatcherT>
+    where
+        InnerT: Debug,
+        OuterT: Debug,
+        ExtractorT: Fn(&OuterT) -> InnerT,
+        MatcherT: Matcher<ActualT = InnerT>,
+    {
+        type ActualT = OuterT;
+
+        fn matches(&self, actual: &OuterT) -> MatcherResult {
+            self.inner.matches(&(self.extractor)(actual))
+        }
+
+        fn describe(&self, matcher_result: MatcherResult) -> String {
+            format!(
+                "has property `{}`, which {}",
+                self.property_desc,
+                self.inner.describe(matcher_result)
+            )
+        }
+
+        fn explain_match(&self, actual: &OuterT) -> String {
+            let actual_inner = (self.extractor)(actual);
+            format!(
+                "whose property `{}` is `{:#?}`, {}",
+                self.property_desc,
+                actual_inner,
+                self.inner.explain_match(&actual_inner)
+            )
+        }
+    }
+
+    /// **For internal use only. API stablility is not guaranteed!**
+    #[doc(hidden)]
+    pub fn property_ref_matcher<OuterT, InnerT, MatcherT>(
+        extractor: fn(&OuterT) -> &InnerT,
+        property_desc: &'static str,
+        inner: MatcherT,
+    ) -> impl Matcher<ActualT = OuterT>
+    where
+        OuterT: Debug,
+        InnerT: Debug + ?Sized,
+        MatcherT: Matcher<ActualT = InnerT>,
+    {
+        PropertyRefMatcher { extractor, property_desc, inner }
+    }
+
+    struct PropertyRefMatcher<InnerT: ?Sized, OuterT, MatcherT> {
+        extractor: fn(&OuterT) -> &InnerT,
+        property_desc: &'static str,
+        inner: MatcherT,
+    }
+
+    impl<InnerT: Debug + ?Sized, OuterT: Debug, MatcherT: Matcher<ActualT = InnerT>> Matcher
+        for PropertyRefMatcher<InnerT, OuterT, MatcherT>
+    {
+        type ActualT = OuterT;
+
+        fn matches(&self, actual: &OuterT) -> MatcherResult {
+            self.inner.matches((self.extractor)(actual))
+        }
+
+        fn describe(&self, matcher_result: MatcherResult) -> String {
+            format!(
+                "has property `{}`, which {}",
+                self.property_desc,
+                self.inner.describe(matcher_result)
+            )
+        }
+
+        fn explain_match(&self, actual: &OuterT) -> String {
+            let actual_inner = (self.extractor)(actual);
+            format!(
+                "whose property `{}` is `{:#?}`, {}",
+                self.property_desc,
+                actual_inner,
+                self.inner.explain_match(actual_inner)
+            )
+        }
+    }
+}
diff --git a/src/matchers/some_matcher.rs b/src/matchers/some_matcher.rs
new file mode 100644
index 0000000..a6ce021
--- /dev/null
+++ b/src/matchers/some_matcher.rs
@@ -0,0 +1,162 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::{fmt::Debug, marker::PhantomData};
+
+/// Matches an `Option` containing a value matched by `inner`.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(Some("Some value"), some(eq("Some value")))?;  // Passes
+/// #     Ok(())
+/// # }
+/// # fn should_fail_1() -> Result<()> {
+/// verify_that!(None::<&str>, some(eq("Some value")))?;   // Fails
+/// #     Ok(())
+/// # }
+/// # fn should_fail_2() -> Result<()> {
+/// verify_that!(Some("Some value"), some(eq("Some other value")))?;   // Fails
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # should_fail_1().unwrap_err();
+/// # should_fail_2().unwrap_err();
+/// ```
+pub fn some<T: Debug>(inner: impl Matcher<ActualT = T>) -> impl Matcher<ActualT = Option<T>> {
+    SomeMatcher { inner, phantom: Default::default() }
+}
+
+struct SomeMatcher<T, InnerMatcherT> {
+    inner: InnerMatcherT,
+    phantom: PhantomData<T>,
+}
+
+impl<T: Debug, InnerMatcherT: Matcher<ActualT = T>> Matcher for SomeMatcher<T, InnerMatcherT> {
+    type ActualT = Option<T>;
+
+    fn matches(&self, actual: &Option<T>) -> MatcherResult {
+        actual.as_ref().map(|v| self.inner.matches(v)).unwrap_or(MatcherResult::NoMatch)
+    }
+
+    fn explain_match(&self, actual: &Option<T>) -> String {
+        match (self.matches(actual), actual) {
+            (_, Some(t)) => format!("which has a value {}", self.inner.explain_match(t)),
+            (_, None) => "which is None".to_string(),
+        }
+    }
+
+    fn describe(&self, matcher_result: MatcherResult) -> String {
+        match matcher_result {
+            MatcherResult::Match => {
+                format!("has a value which {}", self.inner.describe(MatcherResult::Match))
+            }
+            MatcherResult::NoMatch => {
+                format!(
+                    "is None or has a value which {}",
+                    self.inner.describe(MatcherResult::NoMatch)
+                )
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::some;
+    use crate::matcher::{Matcher, MatcherResult};
+    use crate::prelude::*;
+    use indoc::indoc;
+
+    #[test]
+    fn some_matches_option_with_value() -> Result<()> {
+        let matcher = some(eq(1));
+
+        let result = matcher.matches(&Some(1));
+
+        verify_that!(result, eq(MatcherResult::Match))
+    }
+
+    #[test]
+    fn some_does_not_match_option_with_wrong_value() -> Result<()> {
+        let matcher = some(eq(1));
+
+        let result = matcher.matches(&Some(0));
+
+        verify_that!(result, eq(MatcherResult::NoMatch))
+    }
+
+    #[test]
+    fn some_does_not_match_option_with_none() -> Result<()> {
+        let matcher = some(eq(1));
+
+        let result = matcher.matches(&None);
+
+        verify_that!(result, eq(MatcherResult::NoMatch))
+    }
+
+    #[test]
+    fn some_full_error_message() -> Result<()> {
+        let result = verify_that!(Some(2), some(eq(1)));
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc!(
+                "
+                    Value of: Some(2)
+                    Expected: has a value which is equal to 1
+                    Actual: Some(2),
+                      which has a value which isn't equal to 1
+                "
+            ))))
+        )
+    }
+
+    #[test]
+    fn some_describe_matches() -> Result<()> {
+        verify_that!(
+            some(eq(1)).describe(MatcherResult::Match),
+            eq("has a value which is equal to 1")
+        )
+    }
+
+    #[test]
+    fn some_describe_does_not_match() -> Result<()> {
+        verify_that!(
+            some(eq(1)).describe(MatcherResult::NoMatch),
+            eq("is None or has a value which isn't equal to 1")
+        )
+    }
+
+    #[test]
+    fn some_explain_match_with_none() -> Result<()> {
+        verify_that!(some(eq(1)).explain_match(&None), displays_as(eq("which is None")))
+    }
+
+    #[test]
+    fn some_explain_match_with_some_success() -> Result<()> {
+        verify_that!(
+            some(eq(1)).explain_match(&Some(1)),
+            displays_as(eq("which has a value which is equal to 1"))
+        )
+    }
+
+    #[test]
+    fn some_explain_match_with_some_fail() -> Result<()> {
+        verify_that!(
+            some(eq(1)).explain_match(&Some(2)),
+            displays_as(eq("which has a value which isn't equal to 1"))
+        )
+    }
+}
diff --git a/src/matchers/str_matcher.rs b/src/matchers/str_matcher.rs
new file mode 100644
index 0000000..3a4e2e9
--- /dev/null
+++ b/src/matchers/str_matcher.rs
@@ -0,0 +1,1234 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::{
+    matcher::{Matcher, MatcherResult},
+    matcher_support::{
+        edit_distance,
+        summarize_diff::{create_diff, create_diff_reversed},
+    },
+    matchers::{eq_deref_of_matcher::EqDerefOfMatcher, eq_matcher::EqMatcher},
+};
+use std::borrow::Cow;
+use std::fmt::Debug;
+use std::marker::PhantomData;
+use std::ops::Deref;
+
+/// Matches a string containing a given substring.
+///
+/// Both the actual value and the expected substring may be either a `String` or
+/// a string reference.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass_1() -> Result<()> {
+/// verify_that!("Some value", contains_substring("Some"))?;  // Passes
+/// #     Ok(())
+/// # }
+/// # fn should_fail() -> Result<()> {
+/// verify_that!("Another value", contains_substring("Some"))?;   // Fails
+/// #     Ok(())
+/// # }
+/// # fn should_pass_2() -> Result<()> {
+/// verify_that!("Some value".to_string(), contains_substring("value"))?;   // Passes
+/// verify_that!("Some value", contains_substring("value".to_string()))?;   // Passes
+/// #     Ok(())
+/// # }
+/// # should_pass_1().unwrap();
+/// # should_fail().unwrap_err();
+/// # should_pass_2().unwrap();
+/// ```
+///
+/// See the [`StrMatcherConfigurator`] extension trait for more options on how
+/// the string is matched.
+///
+/// > Note on memory use: In most cases, this matcher does not allocate memory
+/// > when matching strings. However, it must allocate copies of both the actual
+/// > and expected values when matching strings while
+/// > [`ignoring_ascii_case`][StrMatcherConfigurator::ignoring_ascii_case] is
+/// > set.
+pub fn contains_substring<A: ?Sized, T>(expected: T) -> StrMatcher<A, T> {
+    StrMatcher {
+        configuration: Configuration { mode: MatchMode::Contains, ..Default::default() },
+        expected,
+        phantom: Default::default(),
+    }
+}
+
+/// Matches a string which starts with the given prefix.
+///
+/// Both the actual value and the expected prefix may be either a `String` or
+/// a string reference.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass_1() -> Result<()> {
+/// verify_that!("Some value", starts_with("Some"))?;  // Passes
+/// #     Ok(())
+/// # }
+/// # fn should_fail_1() -> Result<()> {
+/// verify_that!("Another value", starts_with("Some"))?;   // Fails
+/// #     Ok(())
+/// # }
+/// # fn should_fail_2() -> Result<()> {
+/// verify_that!("Some value", starts_with("value"))?;  // Fails
+/// #     Ok(())
+/// # }
+/// # fn should_pass_2() -> Result<()> {
+/// verify_that!("Some value".to_string(), starts_with("Some"))?;   // Passes
+/// verify_that!("Some value", starts_with("Some".to_string()))?;   // Passes
+/// #     Ok(())
+/// # }
+/// # should_pass_1().unwrap();
+/// # should_fail_1().unwrap_err();
+/// # should_fail_2().unwrap_err();
+/// # should_pass_2().unwrap();
+/// ```
+///
+/// See the [`StrMatcherConfigurator`] extension trait for more options on how
+/// the string is matched.
+pub fn starts_with<A: ?Sized, T>(expected: T) -> StrMatcher<A, T> {
+    StrMatcher {
+        configuration: Configuration { mode: MatchMode::StartsWith, ..Default::default() },
+        expected,
+        phantom: Default::default(),
+    }
+}
+
+/// Matches a string which ends with the given suffix.
+///
+/// Both the actual value and the expected suffix may be either a `String` or
+/// a string reference.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass_1() -> Result<()> {
+/// verify_that!("Some value", ends_with("value"))?;  // Passes
+/// #     Ok(())
+/// # }
+/// # fn should_fail_1() -> Result<()> {
+/// verify_that!("Some value", ends_with("other value"))?;   // Fails
+/// #     Ok(())
+/// # }
+/// # fn should_fail_2() -> Result<()> {
+/// verify_that!("Some value", ends_with("Some"))?;  // Fails
+/// #     Ok(())
+/// # }
+/// # fn should_pass_2() -> Result<()> {
+/// verify_that!("Some value".to_string(), ends_with("value"))?;   // Passes
+/// verify_that!("Some value", ends_with("value".to_string()))?;   // Passes
+/// #     Ok(())
+/// # }
+/// # should_pass_1().unwrap();
+/// # should_fail_1().unwrap_err();
+/// # should_fail_2().unwrap_err();
+/// # should_pass_2().unwrap();
+/// ```
+///
+/// See the [`StrMatcherConfigurator`] extension trait for more options on how
+/// the string is matched.
+pub fn ends_with<A: ?Sized, T>(expected: T) -> StrMatcher<A, T> {
+    StrMatcher {
+        configuration: Configuration { mode: MatchMode::EndsWith, ..Default::default() },
+        expected,
+        phantom: Default::default(),
+    }
+}
+
+/// Extension trait to configure [`StrMatcher`].
+///
+/// Matchers which match against string values and, through configuration,
+/// specialise to [`StrMatcher`] implement this trait. That includes
+/// [`EqMatcher`] and [`StrMatcher`].
+pub trait StrMatcherConfigurator<ActualT: ?Sized, ExpectedT> {
+    /// Configures the matcher to ignore any leading whitespace in either the
+    /// actual or the expected value.
+    ///
+    /// Whitespace is defined as in [`str::trim_start`].
+    ///
+    /// ```
+    /// # use googletest::prelude::*;
+    /// # fn should_pass() -> Result<()> {
+    /// verify_that!("A string", eq("   A string").ignoring_leading_whitespace())?; // Passes
+    /// verify_that!("   A string", eq("A string").ignoring_leading_whitespace())?; // Passes
+    /// #     Ok(())
+    /// # }
+    /// # should_pass().unwrap();
+    /// ```
+    ///
+    /// When all other configuration options are left as the defaults, this is
+    /// equivalent to invoking [`str::trim_start`] on both the expected and
+    /// actual value.
+    fn ignoring_leading_whitespace(self) -> StrMatcher<ActualT, ExpectedT>;
+
+    /// Configures the matcher to ignore any trailing whitespace in either the
+    /// actual or the expected value.
+    ///
+    /// Whitespace is defined as in [`str::trim_end`].
+    ///
+    /// ```
+    /// # use googletest::prelude::*;
+    /// # fn should_pass() -> Result<()> {
+    /// verify_that!("A string", eq("A string   ").ignoring_trailing_whitespace())?; // Passes
+    /// verify_that!("A string   ", eq("A string").ignoring_trailing_whitespace())?; // Passes
+    /// #     Ok(())
+    /// # }
+    /// # should_pass().unwrap();
+    /// ```
+    ///
+    /// When all other configuration options are left as the defaults, this is
+    /// equivalent to invoking [`str::trim_end`] on both the expected and
+    /// actual value.
+    fn ignoring_trailing_whitespace(self) -> StrMatcher<ActualT, ExpectedT>;
+
+    /// Configures the matcher to ignore both leading and trailing whitespace in
+    /// either the actual or the expected value.
+    ///
+    /// Whitespace is defined as in [`str::trim`].
+    ///
+    /// ```
+    /// # use googletest::prelude::*;
+    /// # fn should_pass() -> Result<()> {
+    /// verify_that!("A string", eq("   A string   ").ignoring_outer_whitespace())?; // Passes
+    /// verify_that!("   A string   ", eq("A string").ignoring_outer_whitespace())?; // Passes
+    /// #     Ok(())
+    /// # }
+    /// # should_pass().unwrap();
+    /// ```
+    ///
+    /// This is equivalent to invoking both
+    /// [`ignoring_leading_whitespace`][StrMatcherConfigurator::ignoring_leading_whitespace] and
+    /// [`ignoring_trailing_whitespace`][StrMatcherConfigurator::ignoring_trailing_whitespace].
+    ///
+    /// When all other configuration options are left as the defaults, this is
+    /// equivalent to invoking [`str::trim`] on both the expected and actual
+    /// value.
+    fn ignoring_outer_whitespace(self) -> StrMatcher<ActualT, ExpectedT>;
+
+    /// Configures the matcher to ignore ASCII case when comparing values.
+    ///
+    /// This uses the same rules for case as [`str::eq_ignore_ascii_case`].
+    ///
+    /// ```
+    /// # use googletest::prelude::*;
+    /// # fn should_pass() -> Result<()> {
+    /// verify_that!("Some value", eq("SOME VALUE").ignoring_ascii_case())?;  // Passes
+    /// #     Ok(())
+    /// # }
+    /// # fn should_fail() -> Result<()> {
+    /// verify_that!("Another value", eq("Some value").ignoring_ascii_case())?;   // Fails
+    /// #     Ok(())
+    /// # }
+    /// # should_pass().unwrap();
+    /// # should_fail().unwrap_err();
+    /// ```
+    ///
+    /// This is **not guaranteed** to match strings with differing upper/lower
+    /// case characters outside of the codepoints 0-127 covered by ASCII.
+    fn ignoring_ascii_case(self) -> StrMatcher<ActualT, ExpectedT>;
+
+    /// Configures the matcher to match only strings which otherwise satisfy the
+    /// conditions a number times matched by the matcher `times`.
+    ///
+    /// ```
+    /// # use googletest::prelude::*;
+    /// # fn should_pass() -> Result<()> {
+    /// verify_that!("Some value\nSome value", contains_substring("value").times(eq(2)))?; // Passes
+    /// #     Ok(())
+    /// # }
+    /// # fn should_fail() -> Result<()> {
+    /// verify_that!("Some value", contains_substring("value").times(eq(2)))?; // Fails
+    /// #     Ok(())
+    /// # }
+    /// # should_pass().unwrap();
+    /// # should_fail().unwrap_err();
+    /// ```
+    ///
+    /// The matched substrings must be disjoint from one another to be counted.
+    /// For example:
+    ///
+    /// ```
+    /// # use googletest::prelude::*;
+    /// # fn should_fail() -> Result<()> {
+    /// // Fails: substrings distinct but not disjoint!
+    /// verify_that!("ababab", contains_substring("abab").times(eq(2)))?;
+    /// #     Ok(())
+    /// # }
+    /// # should_fail().unwrap_err();
+    /// ```
+    ///
+    /// This is only meaningful when the matcher was constructed with
+    /// [`contains_substring`]. This method will panic when it is used with any
+    /// other matcher construction.
+    fn times(
+        self,
+        times: impl Matcher<ActualT = usize> + 'static,
+    ) -> StrMatcher<ActualT, ExpectedT>;
+}
+
+/// A matcher which matches equality or containment of a string-like value in a
+/// configurable way.
+///
+/// The following matcher methods instantiate this:
+///
+///  * [`eq`][crate::matchers::eq_matcher::eq],
+///  * [`contains_substring`],
+///  * [`starts_with`],
+///  * [`ends_with`].
+pub struct StrMatcher<ActualT: ?Sized, ExpectedT> {
+    expected: ExpectedT,
+    configuration: Configuration,
+    phantom: PhantomData<ActualT>,
+}
+
+impl<ExpectedT, ActualT> Matcher for StrMatcher<ActualT, ExpectedT>
+where
+    ExpectedT: Deref<Target = str> + Debug,
+    ActualT: AsRef<str> + Debug + ?Sized,
+{
+    type ActualT = ActualT;
+
+    fn matches(&self, actual: &ActualT) -> MatcherResult {
+        self.configuration.do_strings_match(self.expected.deref(), actual.as_ref()).into()
+    }
+
+    fn describe(&self, matcher_result: MatcherResult) -> String {
+        self.configuration.describe(matcher_result, self.expected.deref())
+    }
+
+    fn explain_match(&self, actual: &ActualT) -> String {
+        self.configuration.explain_match(self.expected.deref(), actual.as_ref())
+    }
+}
+
+impl<ActualT: ?Sized, ExpectedT, MatcherT: Into<StrMatcher<ActualT, ExpectedT>>>
+    StrMatcherConfigurator<ActualT, ExpectedT> for MatcherT
+{
+    fn ignoring_leading_whitespace(self) -> StrMatcher<ActualT, ExpectedT> {
+        let existing = self.into();
+        StrMatcher {
+            configuration: existing.configuration.ignoring_leading_whitespace(),
+            ..existing
+        }
+    }
+
+    fn ignoring_trailing_whitespace(self) -> StrMatcher<ActualT, ExpectedT> {
+        let existing = self.into();
+        StrMatcher {
+            configuration: existing.configuration.ignoring_trailing_whitespace(),
+            ..existing
+        }
+    }
+
+    fn ignoring_outer_whitespace(self) -> StrMatcher<ActualT, ExpectedT> {
+        let existing = self.into();
+        StrMatcher { configuration: existing.configuration.ignoring_outer_whitespace(), ..existing }
+    }
+
+    fn ignoring_ascii_case(self) -> StrMatcher<ActualT, ExpectedT> {
+        let existing = self.into();
+        StrMatcher { configuration: existing.configuration.ignoring_ascii_case(), ..existing }
+    }
+
+    fn times(
+        self,
+        times: impl Matcher<ActualT = usize> + 'static,
+    ) -> StrMatcher<ActualT, ExpectedT> {
+        let existing = self.into();
+        if !matches!(existing.configuration.mode, MatchMode::Contains) {
+            panic!("The times() configurator is only meaningful with contains_substring().");
+        }
+        StrMatcher { configuration: existing.configuration.times(times), ..existing }
+    }
+}
+
+impl<A: ?Sized, T: Deref<Target = str>> From<EqMatcher<A, T>> for StrMatcher<A, T> {
+    fn from(value: EqMatcher<A, T>) -> Self {
+        Self::with_default_config(value.expected)
+    }
+}
+
+impl<A: ?Sized, T: Deref<Target = str>> From<EqDerefOfMatcher<A, T>> for StrMatcher<A, T> {
+    fn from(value: EqDerefOfMatcher<A, T>) -> Self {
+        Self::with_default_config(value.expected)
+    }
+}
+
+impl<A: ?Sized, T> StrMatcher<A, T> {
+    /// Returns a [`StrMatcher`] with a default configuration to match against
+    /// the given expected value.
+    ///
+    /// This default configuration is sensitive to whitespace and case.
+    fn with_default_config(expected: T) -> Self {
+        Self { expected, configuration: Default::default(), phantom: Default::default() }
+    }
+}
+
+// Holds all the information on how the expected and actual strings are to be
+// compared. Its associated functions perform the actual matching operations
+// on string references. The struct and comparison methods therefore need not be
+// parameterised, saving compilation time and binary size on monomorphisation.
+//
+// The default value represents exact equality of the strings.
+struct Configuration {
+    mode: MatchMode,
+    ignore_leading_whitespace: bool,
+    ignore_trailing_whitespace: bool,
+    case_policy: CasePolicy,
+    times: Option<Box<dyn Matcher<ActualT = usize>>>,
+}
+
+#[derive(Clone)]
+enum MatchMode {
+    Equals,
+    Contains,
+    StartsWith,
+    EndsWith,
+}
+
+impl MatchMode {
+    fn to_diff_mode(&self) -> edit_distance::Mode {
+        match self {
+            MatchMode::StartsWith | MatchMode::EndsWith => edit_distance::Mode::Prefix,
+            MatchMode::Contains => edit_distance::Mode::Contains,
+            MatchMode::Equals => edit_distance::Mode::Exact,
+        }
+    }
+}
+
+#[derive(Clone)]
+enum CasePolicy {
+    Respect,
+    IgnoreAscii,
+}
+
+impl Configuration {
+    // The entry point for all string matching. StrMatcher::matches redirects
+    // immediately to this function.
+    fn do_strings_match(&self, expected: &str, actual: &str) -> bool {
+        let (expected, actual) =
+            match (self.ignore_leading_whitespace, self.ignore_trailing_whitespace) {
+                (true, true) => (expected.trim(), actual.trim()),
+                (true, false) => (expected.trim_start(), actual.trim_start()),
+                (false, true) => (expected.trim_end(), actual.trim_end()),
+                (false, false) => (expected, actual),
+            };
+        match self.mode {
+            MatchMode::Equals => match self.case_policy {
+                CasePolicy::Respect => expected == actual,
+                CasePolicy::IgnoreAscii => expected.eq_ignore_ascii_case(actual),
+            },
+            MatchMode::Contains => match self.case_policy {
+                CasePolicy::Respect => self.does_containment_match(actual, expected),
+                CasePolicy::IgnoreAscii => self.does_containment_match(
+                    actual.to_ascii_lowercase().as_str(),
+                    expected.to_ascii_lowercase().as_str(),
+                ),
+            },
+            MatchMode::StartsWith => match self.case_policy {
+                CasePolicy::Respect => actual.starts_with(expected),
+                CasePolicy::IgnoreAscii => {
+                    actual.len() >= expected.len()
+                        && actual[..expected.len()].eq_ignore_ascii_case(expected)
+                }
+            },
+            MatchMode::EndsWith => match self.case_policy {
+                CasePolicy::Respect => actual.ends_with(expected),
+                CasePolicy::IgnoreAscii => {
+                    actual.len() >= expected.len()
+                        && actual[actual.len() - expected.len()..].eq_ignore_ascii_case(expected)
+                }
+            },
+        }
+    }
+
+    // Returns whether actual contains expected a number of times matched by the
+    // matcher self.times. Does not take other configuration into account.
+    fn does_containment_match(&self, actual: &str, expected: &str) -> bool {
+        if let Some(times) = self.times.as_ref() {
+            // Split returns an iterator over the "boundaries" left and right of the
+            // substring to be matched, of which there is one more than the number of
+            // substrings.
+            matches!(times.matches(&(actual.split(expected).count() - 1)), MatcherResult::Match)
+        } else {
+            actual.contains(expected)
+        }
+    }
+
+    // StrMatcher::describe redirects immediately to this function.
+    fn describe(&self, matcher_result: MatcherResult, expected: &str) -> String {
+        let mut addenda: Vec<Cow<'static, str>> = Vec::with_capacity(3);
+        match (self.ignore_leading_whitespace, self.ignore_trailing_whitespace) {
+            (true, true) => addenda.push("ignoring leading and trailing whitespace".into()),
+            (true, false) => addenda.push("ignoring leading whitespace".into()),
+            (false, true) => addenda.push("ignoring trailing whitespace".into()),
+            (false, false) => {}
+        }
+        match self.case_policy {
+            CasePolicy::Respect => {}
+            CasePolicy::IgnoreAscii => addenda.push("ignoring ASCII case".into()),
+        }
+        if let Some(times) = self.times.as_ref() {
+            addenda.push(format!("count {}", times.describe(matcher_result)).into());
+        }
+        let extra =
+            if !addenda.is_empty() { format!(" ({})", addenda.join(", ")) } else { "".into() };
+        let match_mode_description = match self.mode {
+            MatchMode::Equals => match matcher_result {
+                MatcherResult::Match => "is equal to",
+                MatcherResult::NoMatch => "isn't equal to",
+            },
+            MatchMode::Contains => match matcher_result {
+                MatcherResult::Match => "contains a substring",
+                MatcherResult::NoMatch => "does not contain a substring",
+            },
+            MatchMode::StartsWith => match matcher_result {
+                MatcherResult::Match => "starts with prefix",
+                MatcherResult::NoMatch => "does not start with",
+            },
+            MatchMode::EndsWith => match matcher_result {
+                MatcherResult::Match => "ends with suffix",
+                MatcherResult::NoMatch => "does not end with",
+            },
+        };
+        format!("{match_mode_description} {expected:?}{extra}")
+    }
+
+    fn explain_match(&self, expected: &str, actual: &str) -> String {
+        let default_explanation = format!(
+            "which {}",
+            self.describe(self.do_strings_match(expected, actual).into(), expected)
+        );
+        if !expected.contains('\n') || !actual.contains('\n') {
+            return default_explanation;
+        }
+
+        if self.ignore_leading_whitespace {
+            // TODO - b/283448414 : Support StrMatcher with ignore_leading_whitespace.
+            return default_explanation;
+        }
+
+        if self.ignore_trailing_whitespace {
+            // TODO - b/283448414 : Support StrMatcher with ignore_trailing_whitespace.
+            return default_explanation;
+        }
+
+        if self.times.is_some() {
+            // TODO - b/283448414 : Support StrMatcher with times.
+            return default_explanation;
+        }
+        if matches!(self.case_policy, CasePolicy::IgnoreAscii) {
+            // TODO - b/283448414 : Support StrMatcher with ignore ascii case policy.
+            return default_explanation;
+        }
+        if self.do_strings_match(expected, actual) {
+            // TODO - b/283448414 : Consider supporting debug difference if the
+            // strings match. This can be useful when a small contains is found
+            // in a long string.
+            return default_explanation;
+        }
+
+        let diff = match self.mode {
+            MatchMode::Equals | MatchMode::StartsWith | MatchMode::Contains => {
+                // TODO(b/287632452): Also consider improving the output in MatchMode::Contains
+                // when the substring begins or ends in the middle of a line of the actual
+                // value.
+                create_diff(actual, expected, self.mode.to_diff_mode())
+            }
+            MatchMode::EndsWith => create_diff_reversed(actual, expected, self.mode.to_diff_mode()),
+        };
+
+        format!("{default_explanation}\n{diff}",)
+    }
+
+    fn ignoring_leading_whitespace(self) -> Self {
+        Self { ignore_leading_whitespace: true, ..self }
+    }
+
+    fn ignoring_trailing_whitespace(self) -> Self {
+        Self { ignore_trailing_whitespace: true, ..self }
+    }
+
+    fn ignoring_outer_whitespace(self) -> Self {
+        Self { ignore_leading_whitespace: true, ignore_trailing_whitespace: true, ..self }
+    }
+
+    fn ignoring_ascii_case(self) -> Self {
+        Self { case_policy: CasePolicy::IgnoreAscii, ..self }
+    }
+
+    fn times(self, times: impl Matcher<ActualT = usize> + 'static) -> Self {
+        Self { times: Some(Box::new(times)), ..self }
+    }
+}
+
+impl Default for Configuration {
+    fn default() -> Self {
+        Self {
+            mode: MatchMode::Equals,
+            ignore_leading_whitespace: false,
+            ignore_trailing_whitespace: false,
+            case_policy: CasePolicy::Respect,
+            times: None,
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::{contains_substring, ends_with, starts_with, StrMatcher, StrMatcherConfigurator};
+    use crate::matcher::{Matcher, MatcherResult};
+    use crate::prelude::*;
+    use indoc::indoc;
+
+    #[test]
+    fn matches_string_reference_with_equal_string_reference() -> Result<()> {
+        let matcher = StrMatcher::with_default_config("A string");
+        verify_that!("A string", matcher)
+    }
+
+    #[test]
+    fn does_not_match_string_reference_with_non_equal_string_reference() -> Result<()> {
+        let matcher = StrMatcher::with_default_config("Another string");
+        verify_that!("A string", not(matcher))
+    }
+
+    #[test]
+    fn matches_owned_string_with_string_reference() -> Result<()> {
+        let matcher = StrMatcher::with_default_config("A string");
+        let value = "A string".to_string();
+        verify_that!(value, matcher)
+    }
+
+    #[test]
+    fn matches_owned_string_reference_with_string_reference() -> Result<()> {
+        let matcher = StrMatcher::with_default_config("A string");
+        let value = "A string".to_string();
+        verify_that!(&value, matcher)
+    }
+
+    #[test]
+    fn ignores_leading_whitespace_in_expected_when_requested() -> Result<()> {
+        let matcher = StrMatcher::with_default_config(" \n\tA string");
+        verify_that!("A string", matcher.ignoring_leading_whitespace())
+    }
+
+    #[test]
+    fn ignores_leading_whitespace_in_actual_when_requested() -> Result<()> {
+        let matcher = StrMatcher::with_default_config("A string");
+        verify_that!(" \n\tA string", matcher.ignoring_leading_whitespace())
+    }
+
+    #[test]
+    fn does_not_match_unequal_remaining_string_when_ignoring_leading_whitespace() -> Result<()> {
+        let matcher = StrMatcher::with_default_config(" \n\tAnother string");
+        verify_that!("A string", not(matcher.ignoring_leading_whitespace()))
+    }
+
+    #[test]
+    fn remains_sensitive_to_trailing_whitespace_when_ignoring_leading_whitespace() -> Result<()> {
+        let matcher = StrMatcher::with_default_config("A string \n\t");
+        verify_that!("A string", not(matcher.ignoring_leading_whitespace()))
+    }
+
+    #[test]
+    fn ignores_trailing_whitespace_in_expected_when_requested() -> Result<()> {
+        let matcher = StrMatcher::with_default_config("A string \n\t");
+        verify_that!("A string", matcher.ignoring_trailing_whitespace())
+    }
+
+    #[test]
+    fn ignores_trailing_whitespace_in_actual_when_requested() -> Result<()> {
+        let matcher = StrMatcher::with_default_config("A string");
+        verify_that!("A string \n\t", matcher.ignoring_trailing_whitespace())
+    }
+
+    #[test]
+    fn does_not_match_unequal_remaining_string_when_ignoring_trailing_whitespace() -> Result<()> {
+        let matcher = StrMatcher::with_default_config("Another string \n\t");
+        verify_that!("A string", not(matcher.ignoring_trailing_whitespace()))
+    }
+
+    #[test]
+    fn remains_sensitive_to_leading_whitespace_when_ignoring_trailing_whitespace() -> Result<()> {
+        let matcher = StrMatcher::with_default_config(" \n\tA string");
+        verify_that!("A string", not(matcher.ignoring_trailing_whitespace()))
+    }
+
+    #[test]
+    fn ignores_leading_and_trailing_whitespace_in_expected_when_requested() -> Result<()> {
+        let matcher = StrMatcher::with_default_config(" \n\tA string \n\t");
+        verify_that!("A string", matcher.ignoring_outer_whitespace())
+    }
+
+    #[test]
+    fn ignores_leading_and_trailing_whitespace_in_actual_when_requested() -> Result<()> {
+        let matcher = StrMatcher::with_default_config("A string");
+        verify_that!(" \n\tA string \n\t", matcher.ignoring_outer_whitespace())
+    }
+
+    #[test]
+    fn respects_ascii_case_by_default() -> Result<()> {
+        let matcher = StrMatcher::with_default_config("A string");
+        verify_that!("A STRING", not(matcher))
+    }
+
+    #[test]
+    fn ignores_ascii_case_when_requested() -> Result<()> {
+        let matcher = StrMatcher::with_default_config("A string");
+        verify_that!("A STRING", matcher.ignoring_ascii_case())
+    }
+
+    #[test]
+    fn allows_ignoring_leading_whitespace_from_eq() -> Result<()> {
+        verify_that!("A string", eq(" \n\tA string").ignoring_leading_whitespace())
+    }
+
+    #[test]
+    fn allows_ignoring_trailing_whitespace_from_eq() -> Result<()> {
+        verify_that!("A string", eq("A string \n\t").ignoring_trailing_whitespace())
+    }
+
+    #[test]
+    fn allows_ignoring_outer_whitespace_from_eq() -> Result<()> {
+        verify_that!("A string", eq(" \n\tA string \n\t").ignoring_outer_whitespace())
+    }
+
+    #[test]
+    fn allows_ignoring_ascii_case_from_eq() -> Result<()> {
+        verify_that!("A string", eq("A STRING").ignoring_ascii_case())
+    }
+
+    #[test]
+    fn allows_ignoring_ascii_case_from_eq_deref_of_str_slice() -> Result<()> {
+        verify_that!("A string", eq_deref_of("A STRING").ignoring_ascii_case())
+    }
+
+    #[test]
+    fn allows_ignoring_ascii_case_from_eq_deref_of_owned_string() -> Result<()> {
+        verify_that!("A string", eq_deref_of("A STRING".to_string()).ignoring_ascii_case())
+    }
+
+    #[test]
+    fn matches_string_containing_expected_value_in_contains_mode() -> Result<()> {
+        verify_that!("Some string", contains_substring("str"))
+    }
+
+    #[test]
+    fn matches_string_containing_expected_value_in_contains_mode_while_ignoring_ascii_case()
+    -> Result<()> {
+        verify_that!("Some string", contains_substring("STR").ignoring_ascii_case())
+    }
+
+    #[test]
+    fn contains_substring_matches_correct_number_of_substrings() -> Result<()> {
+        verify_that!("Some string", contains_substring("str").times(eq(1)))
+    }
+
+    #[test]
+    fn contains_substring_does_not_match_incorrect_number_of_substrings() -> Result<()> {
+        verify_that!("Some string\nSome string", not(contains_substring("string").times(eq(1))))
+    }
+
+    #[test]
+    fn contains_substring_does_not_match_when_substrings_overlap() -> Result<()> {
+        verify_that!("ababab", not(contains_substring("abab").times(eq(2))))
+    }
+
+    #[test]
+    fn starts_with_matches_string_reference_with_prefix() -> Result<()> {
+        verify_that!("Some value", starts_with("Some"))
+    }
+
+    #[test]
+    fn starts_with_matches_string_reference_with_prefix_ignoring_ascii_case() -> Result<()> {
+        verify_that!("Some value", starts_with("SOME").ignoring_ascii_case())
+    }
+
+    #[test]
+    fn starts_with_does_not_match_wrong_prefix_ignoring_ascii_case() -> Result<()> {
+        verify_that!("Some value", not(starts_with("OTHER").ignoring_ascii_case()))
+    }
+
+    #[test]
+    fn ends_with_does_not_match_short_string_ignoring_ascii_case() -> Result<()> {
+        verify_that!("Some", not(starts_with("OTHER").ignoring_ascii_case()))
+    }
+
+    #[test]
+    fn starts_with_does_not_match_string_without_prefix() -> Result<()> {
+        verify_that!("Some value", not(starts_with("Another")))
+    }
+
+    #[test]
+    fn starts_with_does_not_match_string_with_substring_not_at_beginning() -> Result<()> {
+        verify_that!("Some value", not(starts_with("value")))
+    }
+
+    #[test]
+    fn ends_with_matches_string_reference_with_suffix() -> Result<()> {
+        verify_that!("Some value", ends_with("value"))
+    }
+
+    #[test]
+    fn ends_with_matches_string_reference_with_suffix_ignoring_ascii_case() -> Result<()> {
+        verify_that!("Some value", ends_with("VALUE").ignoring_ascii_case())
+    }
+
+    #[test]
+    fn ends_with_does_not_match_wrong_suffix_ignoring_ascii_case() -> Result<()> {
+        verify_that!("Some value", not(ends_with("OTHER").ignoring_ascii_case()))
+    }
+
+    #[test]
+    fn ends_with_does_not_match_too_short_string_ignoring_ascii_case() -> Result<()> {
+        verify_that!("Some", not(ends_with("OTHER").ignoring_ascii_case()))
+    }
+
+    #[test]
+    fn ends_with_does_not_match_string_without_suffix() -> Result<()> {
+        verify_that!("Some value", not(ends_with("other value")))
+    }
+
+    #[test]
+    fn ends_with_does_not_match_string_with_substring_not_at_end() -> Result<()> {
+        verify_that!("Some value", not(ends_with("Some")))
+    }
+
+    #[test]
+    fn describes_itself_for_matching_result() -> Result<()> {
+        let matcher: StrMatcher<&str, _> = StrMatcher::with_default_config("A string");
+        verify_that!(
+            Matcher::describe(&matcher, MatcherResult::Match),
+            eq("is equal to \"A string\"")
+        )
+    }
+
+    #[test]
+    fn describes_itself_for_non_matching_result() -> Result<()> {
+        let matcher: StrMatcher<&str, _> = StrMatcher::with_default_config("A string");
+        verify_that!(
+            Matcher::describe(&matcher, MatcherResult::NoMatch),
+            eq("isn't equal to \"A string\"")
+        )
+    }
+
+    #[test]
+    fn describes_itself_for_matching_result_ignoring_leading_whitespace() -> Result<()> {
+        let matcher: StrMatcher<&str, _> =
+            StrMatcher::with_default_config("A string").ignoring_leading_whitespace();
+        verify_that!(
+            Matcher::describe(&matcher, MatcherResult::Match),
+            eq("is equal to \"A string\" (ignoring leading whitespace)")
+        )
+    }
+
+    #[test]
+    fn describes_itself_for_non_matching_result_ignoring_leading_whitespace() -> Result<()> {
+        let matcher: StrMatcher<&str, _> =
+            StrMatcher::with_default_config("A string").ignoring_leading_whitespace();
+        verify_that!(
+            Matcher::describe(&matcher, MatcherResult::NoMatch),
+            eq("isn't equal to \"A string\" (ignoring leading whitespace)")
+        )
+    }
+
+    #[test]
+    fn describes_itself_for_matching_result_ignoring_trailing_whitespace() -> Result<()> {
+        let matcher: StrMatcher<&str, _> =
+            StrMatcher::with_default_config("A string").ignoring_trailing_whitespace();
+        verify_that!(
+            Matcher::describe(&matcher, MatcherResult::Match),
+            eq("is equal to \"A string\" (ignoring trailing whitespace)")
+        )
+    }
+
+    #[test]
+    fn describes_itself_for_matching_result_ignoring_leading_and_trailing_whitespace() -> Result<()>
+    {
+        let matcher: StrMatcher<&str, _> =
+            StrMatcher::with_default_config("A string").ignoring_outer_whitespace();
+        verify_that!(
+            Matcher::describe(&matcher, MatcherResult::Match),
+            eq("is equal to \"A string\" (ignoring leading and trailing whitespace)")
+        )
+    }
+
+    #[test]
+    fn describes_itself_for_matching_result_ignoring_ascii_case() -> Result<()> {
+        let matcher: StrMatcher<&str, _> =
+            StrMatcher::with_default_config("A string").ignoring_ascii_case();
+        verify_that!(
+            Matcher::describe(&matcher, MatcherResult::Match),
+            eq("is equal to \"A string\" (ignoring ASCII case)")
+        )
+    }
+
+    #[test]
+    fn describes_itself_for_matching_result_ignoring_ascii_case_and_leading_whitespace()
+    -> Result<()> {
+        let matcher: StrMatcher<&str, _> = StrMatcher::with_default_config("A string")
+            .ignoring_leading_whitespace()
+            .ignoring_ascii_case();
+        verify_that!(
+            Matcher::describe(&matcher, MatcherResult::Match),
+            eq("is equal to \"A string\" (ignoring leading whitespace, ignoring ASCII case)")
+        )
+    }
+
+    #[test]
+    fn describes_itself_for_matching_result_in_contains_mode() -> Result<()> {
+        let matcher: StrMatcher<&str, _> = contains_substring("A string");
+        verify_that!(
+            Matcher::describe(&matcher, MatcherResult::Match),
+            eq("contains a substring \"A string\"")
+        )
+    }
+
+    #[test]
+    fn describes_itself_for_non_matching_result_in_contains_mode() -> Result<()> {
+        let matcher: StrMatcher<&str, _> = contains_substring("A string");
+        verify_that!(
+            Matcher::describe(&matcher, MatcherResult::NoMatch),
+            eq("does not contain a substring \"A string\"")
+        )
+    }
+
+    #[test]
+    fn describes_itself_with_count_number() -> Result<()> {
+        let matcher: StrMatcher<&str, _> = contains_substring("A string").times(gt(2));
+        verify_that!(
+            Matcher::describe(&matcher, MatcherResult::Match),
+            eq("contains a substring \"A string\" (count is greater than 2)")
+        )
+    }
+
+    #[test]
+    fn describes_itself_for_matching_result_in_starts_with_mode() -> Result<()> {
+        let matcher: StrMatcher<&str, _> = starts_with("A string");
+        verify_that!(
+            Matcher::describe(&matcher, MatcherResult::Match),
+            eq("starts with prefix \"A string\"")
+        )
+    }
+
+    #[test]
+    fn describes_itself_for_non_matching_result_in_starts_with_mode() -> Result<()> {
+        let matcher: StrMatcher<&str, _> = starts_with("A string");
+        verify_that!(
+            Matcher::describe(&matcher, MatcherResult::NoMatch),
+            eq("does not start with \"A string\"")
+        )
+    }
+
+    #[test]
+    fn describes_itself_for_matching_result_in_ends_with_mode() -> Result<()> {
+        let matcher: StrMatcher<&str, _> = ends_with("A string");
+        verify_that!(
+            Matcher::describe(&matcher, MatcherResult::Match),
+            eq("ends with suffix \"A string\"")
+        )
+    }
+
+    #[test]
+    fn describes_itself_for_non_matching_result_in_ends_with_mode() -> Result<()> {
+        let matcher: StrMatcher<&str, _> = ends_with("A string");
+        verify_that!(
+            Matcher::describe(&matcher, MatcherResult::NoMatch),
+            eq("does not end with \"A string\"")
+        )
+    }
+
+    #[test]
+    fn match_explanation_contains_diff_of_strings_if_more_than_one_line() -> Result<()> {
+        let result = verify_that!(
+            indoc!(
+                "
+                    First line
+                    Second line
+                    Third line
+                "
+            ),
+            starts_with(indoc!(
+                "
+                    First line
+                    Second lines
+                    Third line
+                "
+            ))
+        );
+
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc!(
+                "
+                 First line
+                -Second line
+                +Second lines
+                 Third line
+                "
+            ))))
+        )
+    }
+
+    #[test]
+    fn match_explanation_for_starts_with_ignores_trailing_lines_in_actual_string() -> Result<()> {
+        let result = verify_that!(
+            indoc!(
+                "
+                    First line
+                    Second line
+                    Third line
+                    Fourth line
+                "
+            ),
+            starts_with(indoc!(
+                "
+                    First line
+                    Second lines
+                    Third line
+                "
+            ))
+        );
+
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc!(
+                "
+                     First line
+                    -Second line
+                    +Second lines
+                     Third line
+                     <---- remaining lines omitted ---->
+                "
+            ))))
+        )
+    }
+
+    #[test]
+    fn match_explanation_for_starts_with_includes_both_versions_of_differing_last_line()
+    -> Result<()> {
+        let result = verify_that!(
+            indoc!(
+                "
+                    First line
+                    Second line
+                    Third line
+                "
+            ),
+            starts_with(indoc!(
+                "
+                    First line
+                    Second lines
+                "
+            ))
+        );
+
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc!(
+                "
+                     First line
+                    -Second line
+                    +Second lines
+                     <---- remaining lines omitted ---->
+                "
+            ))))
+        )
+    }
+
+    #[test]
+    fn match_explanation_for_ends_with_ignores_leading_lines_in_actual_string() -> Result<()> {
+        let result = verify_that!(
+            indoc!(
+                "
+                    First line
+                    Second line
+                    Third line
+                    Fourth line
+                "
+            ),
+            ends_with(indoc!(
+                "
+                    Second line
+                    Third lines
+                    Fourth line
+                "
+            ))
+        );
+
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc!(
+                "
+                    Difference(-actual / +expected):
+                     <---- remaining lines omitted ---->
+                     Second line
+                    +Third lines
+                    -Third line
+                     Fourth line
+                "
+            ))))
+        )
+    }
+
+    #[test]
+    fn match_explanation_for_contains_substring_ignores_outer_lines_in_actual_string() -> Result<()>
+    {
+        let result = verify_that!(
+            indoc!(
+                "
+                    First line
+                    Second line
+                    Third line
+                    Fourth line
+                    Fifth line
+                "
+            ),
+            contains_substring(indoc!(
+                "
+                    Second line
+                    Third lines
+                    Fourth line
+                "
+            ))
+        );
+
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc!(
+                "
+                    Difference(-actual / +expected):
+                     <---- remaining lines omitted ---->
+                     Second line
+                    +Third lines
+                    -Third line
+                     Fourth line
+                     <---- remaining lines omitted ---->"
+            ))))
+        )
+    }
+
+    #[test]
+    fn match_explanation_for_contains_substring_shows_diff_when_first_and_last_line_are_incomplete()
+    -> Result<()> {
+        let result = verify_that!(
+            indoc!(
+                "
+                    First line
+                    Second line
+                    Third line
+                    Fourth line
+                    Fifth line
+                "
+            ),
+            contains_substring(indoc!(
+                "
+                    line
+                    Third line
+                    Foorth line
+                    Fifth"
+            ))
+        );
+
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc!(
+                "
+                    Difference(-actual / +expected):
+                     <---- remaining lines omitted ---->
+                    +line
+                    -Second line
+                     Third line
+                    +Foorth line
+                    -Fourth line
+                    +Fifth
+                    -Fifth line
+                     <---- remaining lines omitted ---->
+                "
+            ))))
+        )
+    }
+
+    #[test]
+    fn match_explanation_for_eq_does_not_ignore_trailing_lines_in_actual_string() -> Result<()> {
+        let result = verify_that!(
+            indoc!(
+                "
+                    First line
+                    Second line
+                    Third line
+                    Fourth line
+                "
+            ),
+            eq(indoc!(
+                "
+                    First line
+                    Second lines
+                    Third line
+                "
+            ))
+        );
+
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc!(
+                "
+                     First line
+                    -Second line
+                    +Second lines
+                     Third line
+                    -Fourth line
+                "
+            ))))
+        )
+    }
+
+    #[test]
+    fn match_explanation_does_not_show_diff_if_actual_value_is_single_line() -> Result<()> {
+        let result = verify_that!(
+            "First line",
+            starts_with(indoc!(
+                "
+                    Second line
+                    Third line
+                "
+            ))
+        );
+
+        verify_that!(
+            result,
+            err(displays_as(not(contains_substring("Difference(-actual / +expected):"))))
+        )
+    }
+
+    #[test]
+    fn match_explanation_does_not_show_diff_if_expected_value_is_single_line() -> Result<()> {
+        let result = verify_that!(
+            indoc!(
+                "
+                    First line
+                    Second line
+                    Third line
+                "
+            ),
+            starts_with("Second line")
+        );
+
+        verify_that!(
+            result,
+            err(displays_as(not(contains_substring("Difference(-actual / +expected):"))))
+        )
+    }
+}
diff --git a/src/matchers/subset_of_matcher.rs b/src/matchers/subset_of_matcher.rs
new file mode 100644
index 0000000..facee4f
--- /dev/null
+++ b/src/matchers/subset_of_matcher.rs
@@ -0,0 +1,267 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::{fmt::Debug, marker::PhantomData};
+
+/// Matches a container all of whose items are in the given container
+/// `superset`.
+///
+/// The element type `ElementT` must implement `PartialEq` to allow element
+/// comparison.
+///
+/// `ActualT` and `ExpectedT` can each be any container a reference to which
+/// implements `IntoIterator`. They need not be the same container type.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # use std::collections::HashSet;
+/// # fn should_pass_1() -> Result<()> {
+/// let value = vec![1, 2, 3];
+/// verify_that!(value, subset_of([1, 2, 3, 4]))?;  // Passes
+/// #     Ok(())
+/// # }
+/// # fn should_fail() -> Result<()> {
+/// # let value = vec![1, 2, 3];
+/// verify_that!(value, subset_of([1, 2]))?;  // Fails: 3 is not in the superset
+/// #     Ok(())
+/// # }
+/// # should_pass_1().unwrap();
+/// # should_fail().unwrap_err();
+///
+/// # fn should_pass_2() -> Result<()> {
+/// let value: HashSet<i32> = [1, 2, 3].into();
+/// verify_that!(value, subset_of([1, 2, 3]))?;  // Passes
+/// #     Ok(())
+/// # }
+/// # should_pass_2().unwrap();
+/// ```
+///
+/// Item multiplicity in both the actual and expected containers is ignored:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// let value: Vec<i32> = vec![0, 0, 1];
+/// verify_that!(value, subset_of([0, 1]))?;  // Passes
+/// verify_that!(value, subset_of([0, 1, 1]))?;  // Passes
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// One can also verify the contents of a slice by dereferencing it:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// let value = &[1, 2, 3];
+/// verify_that!(*value, subset_of([1, 2, 3]))?;
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// A note on performance: This matcher uses a naive algorithm with a worst-case
+/// runtime proportional to the *product* of the sizes of the actual and
+/// expected containers as well as the time to check equality of each pair of
+/// items. It should not be used on especially large containers.
+pub fn subset_of<ElementT: Debug + PartialEq, ActualT: Debug + ?Sized, ExpectedT: Debug>(
+    superset: ExpectedT,
+) -> impl Matcher<ActualT = ActualT>
+where
+    for<'a> &'a ActualT: IntoIterator<Item = &'a ElementT>,
+    for<'a> &'a ExpectedT: IntoIterator<Item = &'a ElementT>,
+{
+    SubsetOfMatcher::<ActualT, _> { superset, phantom: Default::default() }
+}
+
+struct SubsetOfMatcher<ActualT: ?Sized, ExpectedT> {
+    superset: ExpectedT,
+    phantom: PhantomData<ActualT>,
+}
+
+impl<ElementT: Debug + PartialEq, ActualT: Debug + ?Sized, ExpectedT: Debug> Matcher
+    for SubsetOfMatcher<ActualT, ExpectedT>
+where
+    for<'a> &'a ActualT: IntoIterator<Item = &'a ElementT>,
+    for<'a> &'a ExpectedT: IntoIterator<Item = &'a ElementT>,
+{
+    type ActualT = ActualT;
+
+    fn matches(&self, actual: &ActualT) -> MatcherResult {
+        for actual_item in actual {
+            if self.expected_is_missing(actual_item) {
+                return MatcherResult::NoMatch;
+            }
+        }
+        MatcherResult::Match
+    }
+
+    fn explain_match(&self, actual: &ActualT) -> String {
+        let unexpected_elements = actual
+            .into_iter()
+            .enumerate()
+            .filter(|&(_, actual_item)| self.expected_is_missing(actual_item))
+            .map(|(idx, actual_item)| format!("{actual_item:#?} at #{idx}"))
+            .collect::<Vec<_>>();
+
+        match unexpected_elements.len() {
+            0 => "which no element is unexpected".to_string(),
+            1 => format!("whose element {} is unexpected", &unexpected_elements[0]),
+            _ => format!("whose elements {} are unexpected", unexpected_elements.join(", ")),
+        }
+    }
+
+    fn describe(&self, matcher_result: MatcherResult) -> String {
+        match matcher_result {
+            MatcherResult::Match => format!("is a subset of {:#?}", self.superset),
+            MatcherResult::NoMatch => format!("isn't a subset of {:#?}", self.superset),
+        }
+    }
+}
+
+impl<ActualT: ?Sized, ElementT: PartialEq, ExpectedT> SubsetOfMatcher<ActualT, ExpectedT>
+where
+    for<'a> &'a ExpectedT: IntoIterator<Item = &'a ElementT>,
+{
+    fn expected_is_missing(&self, needle: &ElementT) -> bool {
+        !self.superset.into_iter().any(|item| *item == *needle)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::subset_of;
+    use crate::prelude::*;
+    use indoc::indoc;
+    use std::collections::HashSet;
+
+    #[test]
+    fn subset_of_matches_empty_vec() -> Result<()> {
+        let value: Vec<i32> = vec![];
+        verify_that!(value, subset_of([]))
+    }
+
+    #[test]
+    fn subset_of_matches_vec_with_one_element() -> Result<()> {
+        let value = vec![1];
+        verify_that!(value, subset_of([1]))
+    }
+
+    #[test]
+    fn subset_of_matches_vec_with_two_elements() -> Result<()> {
+        let value = vec![1, 2];
+        verify_that!(value, subset_of([1, 2]))
+    }
+
+    #[test]
+    fn subset_of_matches_vec_when_expected_has_excess_element() -> Result<()> {
+        let value = vec![1, 2];
+        verify_that!(value, subset_of([1, 2, 3]))
+    }
+
+    #[test]
+    fn subset_of_matches_vec_when_expected_has_excess_element_first() -> Result<()> {
+        let value = vec![1, 2];
+        verify_that!(value, subset_of([3, 1, 2]))
+    }
+
+    #[test]
+    fn subset_of_matches_slice_with_one_element() -> Result<()> {
+        let value = &[1];
+        verify_that!(*value, subset_of([1]))
+    }
+
+    #[test]
+    fn subset_of_matches_hash_set_with_one_element() -> Result<()> {
+        let value: HashSet<i32> = [1].into();
+        verify_that!(value, subset_of([1]))
+    }
+
+    #[test]
+    fn subset_of_does_not_match_when_first_element_does_not_match() -> Result<()> {
+        let value = vec![0];
+        verify_that!(value, not(subset_of([1])))
+    }
+
+    #[test]
+    fn subset_of_does_not_match_when_second_element_does_not_match() -> Result<()> {
+        let value = vec![2, 0];
+        verify_that!(value, not(subset_of([2])))
+    }
+
+    #[test]
+    fn subset_of_shows_correct_message_when_first_item_does_not_match() -> Result<()> {
+        let result = verify_that!(vec![0, 2, 3], subset_of([1, 2, 3]));
+
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc!(
+                "
+                    Value of: vec![0, 2, 3]
+                    Expected: is a subset of [
+                        1,
+                        2,
+                        3,
+                    ]
+                    Actual: [0, 2, 3],
+                      whose element 0 at #0 is unexpected
+                "
+            ))))
+        )
+    }
+
+    #[test]
+    fn subset_of_shows_correct_message_when_second_item_does_not_match() -> Result<()> {
+        let result = verify_that!(vec![1, 0, 3], subset_of([1, 2, 3]));
+
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc!(
+                "
+                    Value of: vec![1, 0, 3]
+                    Expected: is a subset of [
+                        1,
+                        2,
+                        3,
+                    ]
+                    Actual: [1, 0, 3],
+                      whose element 0 at #1 is unexpected
+                "
+            ))))
+        )
+    }
+
+    #[test]
+    fn subset_of_shows_correct_message_when_first_two_items_do_not_match() -> Result<()> {
+        let result = verify_that!(vec![0, 0, 3], subset_of([1, 2, 3]));
+
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc!(
+                "
+                    Value of: vec![0, 0, 3]
+                    Expected: is a subset of [
+                        1,
+                        2,
+                        3,
+                    ]
+                    Actual: [0, 0, 3],
+                      whose elements 0 at #0, 0 at #1 are unexpected
+                "
+            ))))
+        )
+    }
+}
diff --git a/src/matchers/superset_of_matcher.rs b/src/matchers/superset_of_matcher.rs
new file mode 100644
index 0000000..8e98015
--- /dev/null
+++ b/src/matchers/superset_of_matcher.rs
@@ -0,0 +1,267 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::matcher::{Matcher, MatcherResult};
+use std::{fmt::Debug, marker::PhantomData};
+
+/// Matches a container containing all of the items in the given container
+/// `subset`.
+///
+/// The element type `ElementT` must implement `PartialEq` to allow element
+/// comparison.
+///
+/// `ActualT` and `ExpectedT` can each be any container a reference to which
+/// implements `IntoIterator`. They need not be the same container type.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # use std::collections::HashSet;
+/// # fn should_pass_1() -> Result<()> {
+/// let value = vec![1, 2, 3];
+/// verify_that!(value, superset_of([1, 2]))?;  // Passes
+/// #     Ok(())
+/// # }
+/// # fn should_fail() -> Result<()> {
+/// # let value = vec![1, 2, 3];
+/// verify_that!(value, superset_of([1, 2, 4]))?;  // Fails: 4 is not in the subset
+/// #     Ok(())
+/// # }
+/// # should_pass_1().unwrap();
+/// # should_fail().unwrap_err();
+///
+/// # fn should_pass_2() -> Result<()> {
+/// let value: HashSet<i32> = [1, 2, 3].into();
+/// verify_that!(value, superset_of([1, 2, 3]))?;  // Passes
+/// #     Ok(())
+/// # }
+/// # should_pass_2().unwrap();
+/// ```
+///
+/// Item multiplicity in both the actual and expected containers is ignored:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// let value: Vec<i32> = vec![0, 0, 1];
+/// verify_that!(value, superset_of([0, 1]))?;  // Passes
+/// verify_that!(value, superset_of([0, 1, 1]))?;  // Passes
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// One can also verify the contents of a slice by dereferencing it:
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// let value = &[1, 2, 3];
+/// verify_that!(*value, superset_of([1, 2, 3]))?;
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// ```
+///
+/// A note on performance: This matcher uses a naive algorithm with a worst-case
+/// runtime proportional to the *product* of the sizes of the actual and
+/// expected containers as well as the time to check equality of each pair of
+/// items. It should not be used on especially large containers.
+pub fn superset_of<ElementT: Debug + PartialEq, ActualT: Debug + ?Sized, ExpectedT: Debug>(
+    subset: ExpectedT,
+) -> impl Matcher<ActualT = ActualT>
+where
+    for<'a> &'a ActualT: IntoIterator<Item = &'a ElementT>,
+    for<'a> &'a ExpectedT: IntoIterator<Item = &'a ElementT>,
+{
+    SupersetOfMatcher::<ActualT, _> { subset, phantom: Default::default() }
+}
+
+struct SupersetOfMatcher<ActualT: ?Sized, ExpectedT> {
+    subset: ExpectedT,
+    phantom: PhantomData<ActualT>,
+}
+
+impl<ElementT: Debug + PartialEq, ActualT: Debug + ?Sized, ExpectedT: Debug> Matcher
+    for SupersetOfMatcher<ActualT, ExpectedT>
+where
+    for<'a> &'a ActualT: IntoIterator<Item = &'a ElementT>,
+    for<'a> &'a ExpectedT: IntoIterator<Item = &'a ElementT>,
+{
+    type ActualT = ActualT;
+
+    fn matches(&self, actual: &ActualT) -> MatcherResult {
+        for expected_item in &self.subset {
+            if actual_is_missing(actual, expected_item) {
+                return MatcherResult::NoMatch;
+            }
+        }
+        MatcherResult::Match
+    }
+
+    fn explain_match(&self, actual: &ActualT) -> String {
+        let missing_items: Vec<_> = self
+            .subset
+            .into_iter()
+            .filter(|expected_item| actual_is_missing(actual, expected_item))
+            .map(|expected_item| format!("{expected_item:#?}"))
+            .collect();
+        match missing_items.len() {
+            0 => "whose no element is missing".to_string(),
+            1 => format!("whose element {} is missing", &missing_items[0]),
+            _ => format!("whose elements {} are missing", missing_items.join(", ")),
+        }
+    }
+
+    fn describe(&self, matcher_result: MatcherResult) -> String {
+        match matcher_result {
+            MatcherResult::Match => format!("is a superset of {:#?}", self.subset),
+            MatcherResult::NoMatch => format!("isn't a superset of {:#?}", self.subset),
+        }
+    }
+}
+
+fn actual_is_missing<ElementT: PartialEq, ActualT: ?Sized>(
+    actual: &ActualT,
+    needle: &ElementT,
+) -> bool
+where
+    for<'a> &'a ActualT: IntoIterator<Item = &'a ElementT>,
+{
+    !actual.into_iter().any(|item| *item == *needle)
+}
+
+#[cfg(test)]
+mod tests {
+    use super::superset_of;
+    use crate::prelude::*;
+    use indoc::indoc;
+    use std::collections::HashSet;
+
+    #[test]
+    fn superset_of_matches_empty_vec() -> Result<()> {
+        let value: Vec<i32> = vec![];
+        verify_that!(value, superset_of([]))
+    }
+
+    #[test]
+    fn superset_of_matches_vec_with_one_element() -> Result<()> {
+        let value = vec![1];
+        verify_that!(value, superset_of([1]))
+    }
+
+    #[test]
+    fn superset_of_matches_vec_with_two_items() -> Result<()> {
+        let value = vec![1, 2];
+        verify_that!(value, superset_of([1, 2]))
+    }
+
+    #[test]
+    fn superset_of_matches_vec_when_actual_has_excess_element() -> Result<()> {
+        let value = vec![1, 2, 3];
+        verify_that!(value, superset_of([1, 2]))
+    }
+
+    #[test]
+    fn superset_of_matches_vec_when_actual_has_excess_element_first() -> Result<()> {
+        let value = vec![3, 1, 2];
+        verify_that!(value, superset_of([1, 2]))
+    }
+
+    #[test]
+    fn superset_of_matches_slice_with_one_element() -> Result<()> {
+        let value = &[1];
+        verify_that!(*value, superset_of([1]))
+    }
+
+    #[test]
+    fn superset_of_matches_hash_set_with_one_element() -> Result<()> {
+        let value: HashSet<i32> = [1].into();
+        verify_that!(value, superset_of([1]))
+    }
+
+    #[test]
+    fn superset_of_does_not_match_when_first_element_does_not_match() -> Result<()> {
+        let value = vec![0];
+        verify_that!(value, not(superset_of([1])))
+    }
+
+    #[test]
+    fn superset_of_does_not_match_when_second_element_does_not_match() -> Result<()> {
+        let value = vec![2];
+        verify_that!(value, not(superset_of([2, 0])))
+    }
+
+    #[test]
+    fn superset_of_shows_correct_message_when_first_item_does_not_match() -> Result<()> {
+        let result = verify_that!(vec![0, 2, 3], superset_of([1, 2, 3]));
+
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc!(
+                "
+                    Value of: vec![0, 2, 3]
+                    Expected: is a superset of [
+                        1,
+                        2,
+                        3,
+                    ]
+                    Actual: [0, 2, 3],
+                      whose element 1 is missing
+                "
+            ))))
+        )
+    }
+
+    #[test]
+    fn superset_of_shows_correct_message_when_second_item_does_not_match() -> Result<()> {
+        let result = verify_that!(vec![1, 0, 3], superset_of([1, 2, 3]));
+
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc!(
+                "
+                    Value of: vec![1, 0, 3]
+                    Expected: is a superset of [
+                        1,
+                        2,
+                        3,
+                    ]
+                    Actual: [1, 0, 3],
+                      whose element 2 is missing
+                "
+            ))))
+        )
+    }
+
+    #[test]
+    fn superset_of_shows_correct_message_when_first_two_items_do_not_match() -> Result<()> {
+        let result = verify_that!(vec![0, 0, 3], superset_of([1, 2, 3]));
+
+        verify_that!(
+            result,
+            err(displays_as(contains_substring(indoc!(
+                "
+                    Value of: vec![0, 0, 3]
+                    Expected: is a superset of [
+                        1,
+                        2,
+                        3,
+                    ]
+                    Actual: [0, 0, 3],
+                      whose elements 1, 2 are missing
+                "
+            ))))
+        )
+    }
+}
diff --git a/src/matchers/tuple_matcher.rs b/src/matchers/tuple_matcher.rs
new file mode 100644
index 0000000..a2e325b
--- /dev/null
+++ b/src/matchers/tuple_matcher.rs
@@ -0,0 +1,207 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// There are no visible documentation elements in this module; the declarative
+// macro is documented at the top level.
+#![doc(hidden)]
+
+/// Functions for use only by the declarative macros in this module.
+///
+/// **For internal use only. API stablility is not guaranteed!**
+#[doc(hidden)]
+pub mod internal {
+    use crate::matcher::{Matcher, MatcherResult};
+    use std::fmt::{Debug, Write};
+
+    /// Replaces the first expression with the second at compile time.
+    ///
+    /// This is used below in repetition sequences where the output must only
+    /// include the same expression repeated the same number of times as the
+    /// macro input.
+    ///
+    /// **For internal use only. API stablility is not guaranteed!**
+    #[doc(hidden)]
+    macro_rules! replace_expr {
+        ($_ignored:tt, $replacement:expr) => {
+            $replacement
+        };
+    }
+
+    // This implementation is provided for completeness, but is completely trivial.
+    // The only actual value which can be supplied is (), which must match.
+    impl Matcher for () {
+        type ActualT = ();
+
+        fn matches(&self, _: &Self::ActualT) -> MatcherResult {
+            MatcherResult::Match
+        }
+
+        fn describe(&self, matcher_result: MatcherResult) -> String {
+            match matcher_result {
+                MatcherResult::Match => "is the empty tuple".into(),
+                MatcherResult::NoMatch => "is not the empty tuple".into(),
+            }
+        }
+    }
+
+    /// Generates a tuple matcher for tuples of a specific length.
+    ///
+    /// **For internal use only. API stablility is not guaranteed!**
+    #[doc(hidden)]
+    macro_rules! tuple_matcher_n {
+        ($([$field_number:tt, $matcher_type:ident, $field_type:ident]),*) => {
+            impl<$($field_type: Debug, $matcher_type: Matcher<ActualT = $field_type>),*>
+                Matcher for ($($matcher_type,)*)
+            {
+                type ActualT = ($($field_type,)*);
+
+                fn matches(&self, actual: &($($field_type,)*)) -> MatcherResult {
+                    $(match self.$field_number.matches(&actual.$field_number) {
+                        MatcherResult::Match => {},
+                        MatcherResult::NoMatch => {
+                            return MatcherResult::NoMatch;
+                        }
+                    })*
+                    MatcherResult::Match
+                }
+
+                fn explain_match(&self, actual: &($($field_type,)*)) -> String {
+                    let mut explanation = format!("which {}", self.describe(self.matches(actual)));
+                    $(match self.$field_number.matches(&actual.$field_number) {
+                        MatcherResult::Match => {},
+                        MatcherResult::NoMatch => {
+                            writeln!(
+                                &mut explanation,
+                                concat!("Element #", $field_number, " is {:?}, {}"),
+                                actual.$field_number,
+                                self.$field_number.explain_match(&actual.$field_number)
+                            ).unwrap();
+                        }
+                    })*
+                    (explanation)
+                }
+
+                fn describe(&self, matcher_result: MatcherResult) -> String {
+                    match matcher_result {
+                        MatcherResult::Match => {
+                            format!(
+                                concat!(
+                                    "is a tuple whose values respectively match:\n",
+                                    $(replace_expr!($field_number, "  {},\n")),*
+                                ),
+                                $(self.$field_number.describe(matcher_result)),*
+                            )
+                        }
+                        MatcherResult::NoMatch => {
+                            format!(
+                                concat!(
+                                    "is a tuple whose values do not respectively match:\n",
+                                    $(replace_expr!($field_number, "  {},\n")),*
+                                ),
+                                $(self.$field_number.describe(MatcherResult::Match)),*
+                            )
+                        }
+                    }
+                }
+            }
+        };
+    }
+
+    tuple_matcher_n!([0, I0, T0]);
+
+    tuple_matcher_n!([0, I0, T0], [1, I1, T1]);
+
+    tuple_matcher_n!([0, I0, T0], [1, I1, T1], [2, I2, T2]);
+
+    tuple_matcher_n!([0, I0, T0], [1, I1, T1], [2, I2, T2], [3, I3, T3]);
+
+    tuple_matcher_n!([0, I0, T0], [1, I1, T1], [2, I2, T2], [3, I3, T3], [4, I4, T4]);
+
+    tuple_matcher_n!([0, I0, T0], [1, I1, T1], [2, I2, T2], [3, I3, T3], [4, I4, T4], [5, I5, T5]);
+
+    tuple_matcher_n!(
+        [0, I0, T0],
+        [1, I1, T1],
+        [2, I2, T2],
+        [3, I3, T3],
+        [4, I4, T4],
+        [5, I5, T5],
+        [6, I6, T6]
+    );
+
+    tuple_matcher_n!(
+        [0, I0, T0],
+        [1, I1, T1],
+        [2, I2, T2],
+        [3, I3, T3],
+        [4, I4, T4],
+        [5, I5, T5],
+        [6, I6, T6],
+        [7, I7, T7]
+    );
+
+    tuple_matcher_n!(
+        [0, I0, T0],
+        [1, I1, T1],
+        [2, I2, T2],
+        [3, I3, T3],
+        [4, I4, T4],
+        [5, I5, T5],
+        [6, I6, T6],
+        [7, I7, T7],
+        [8, I8, T8]
+    );
+
+    tuple_matcher_n!(
+        [0, I0, T0],
+        [1, I1, T1],
+        [2, I2, T2],
+        [3, I3, T3],
+        [4, I4, T4],
+        [5, I5, T5],
+        [6, I6, T6],
+        [7, I7, T7],
+        [8, I8, T8],
+        [9, I9, T9]
+    );
+
+    tuple_matcher_n!(
+        [0, I0, T0],
+        [1, I1, T1],
+        [2, I2, T2],
+        [3, I3, T3],
+        [4, I4, T4],
+        [5, I5, T5],
+        [6, I6, T6],
+        [7, I7, T7],
+        [8, I8, T8],
+        [9, I9, T9],
+        [10, I10, T10]
+    );
+
+    tuple_matcher_n!(
+        [0, I0, T0],
+        [1, I1, T1],
+        [2, I2, T2],
+        [3, I3, T3],
+        [4, I4, T4],
+        [5, I5, T5],
+        [6, I6, T6],
+        [7, I7, T7],
+        [8, I8, T8],
+        [9, I9, T9],
+        [10, I10, T10],
+        [11, I11, T11]
+    );
+}
diff --git a/src/matchers/unordered_elements_are_matcher.rs b/src/matchers/unordered_elements_are_matcher.rs
new file mode 100644
index 0000000..1930697
--- /dev/null
+++ b/src/matchers/unordered_elements_are_matcher.rs
@@ -0,0 +1,1116 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// There are no visible documentation elements in this module; the declarative
+// macro is documented at the top level.
+#![doc(hidden)]
+
+/// Matches a container whose elements in any order have a 1:1 correspondence
+/// with the provided element matchers.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(vec![3, 2, 1], unordered_elements_are![eq(1), ge(2), anything()])?;   // Passes
+/// #     Ok(())
+/// # }
+/// # fn should_fail_1() -> Result<()> {
+/// verify_that!(vec![1], unordered_elements_are![eq(1), ge(2)])?;              // Fails: container has wrong size
+/// #     Ok(())
+/// # }
+/// # fn should_fail_2() -> Result<()> {
+/// verify_that!(vec![3, 2, 1], unordered_elements_are![eq(1), ge(4), eq(2)])?; // Fails: second matcher not matched
+/// #     Ok(())
+/// # }
+/// # fn should_fail_3() -> Result<()> {
+/// verify_that!(vec![3, 2, 1], unordered_elements_are![ge(3), ge(3), ge(3)])?; // Fails: no 1:1 correspondence
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # should_fail_1().unwrap_err();
+/// # should_fail_2().unwrap_err();
+/// # should_fail_3().unwrap_err();
+/// ```
+///
+/// The actual value must be a container implementing [`IntoIterator`]. This
+/// includes standard containers, slices (when dereferenced) and arrays.
+///
+/// This can also match against [`HashMap`][std::collections::HashMap] and
+/// similar collections. The arguments are a sequence of pairs of matchers
+/// corresponding to the keys and their respective values.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # use std::collections::HashMap;
+/// let value: HashMap<u32, &'static str> =
+///     HashMap::from_iter([(1, "One"), (2, "Two"), (3, "Three")]);
+/// verify_that!(
+///     value,
+///     unordered_elements_are![(eq(2), eq("Two")), (eq(1), eq("One")), (eq(3), eq("Three"))]
+/// )
+/// #     .unwrap();
+/// ```
+///
+/// This can also be omitted in [`verify_that!`] macros and replaced with curly
+/// brackets.
+///
+/// ```
+/// # use googletest::prelude::*;
+///  verify_that!(vec![1, 2], {eq(2), eq(1)})
+/// #     .unwrap();
+/// ```
+///
+/// Note: This behavior is only possible in [`verify_that!`] macros. In any
+/// other cases, it is still necessary to use the
+/// [`unordered_elements_are!`][crate::unordered_elements_are] macro.
+///
+/// ```compile_fail
+/// # use googletest::prelude::*;
+/// verify_that!(vec![vec![1,2], vec![3]], {{eq(2), eq(1)}, {eq(3)}})
+/// # .unwrap();
+/// ```
+///
+/// Use this instead:
+/// ```
+/// # use googletest::prelude::*;
+/// verify_that!(vec![vec![1,2], vec![3]],
+///   {unordered_elements_are![eq(2), eq(1)], unordered_elements_are![eq(3)]})
+/// # .unwrap();
+/// ```
+///
+/// This matcher does not support matching directly against an [`Iterator`]. To
+/// match against an iterator, use [`Iterator::collect`] to build a [`Vec`].
+///
+/// The matcher proceeds in three stages:
+///
+/// 1. It first checks whether the actual value is of the right size to possibly
+///    be matched by each of the given matchers. If not, then it immediately
+///    fails explaining that the size is incorrect.
+///
+/// 2. It then checks whether each matcher matches at least one corresponding
+///    element in the actual container and each element in the actual container
+///    is matched by at least one matcher. If not, it fails with a message
+///    indicating which matcher respectively container elements had no
+///    counterparts.
+///
+/// 3. Finally, it checks whether the mapping of matchers to corresponding
+///    actual elements is a 1-1 correspondence and fails if that is not the
+///    case. The failure message then shows the best matching it could find,
+///    including which matchers did not have corresponding unique elements in
+///    the container and which container elements had no corresponding matchers.
+///
+/// [`IntoIterator`]: std::iter::IntoIterator
+/// [`Iterator`]: std::iter::Iterator
+/// [`Iterator::collect`]: std::iter::Iterator::collect
+/// [`Vec`]: std::vec::Vec
+#[macro_export]
+macro_rules! unordered_elements_are {
+    ($(,)?) => {{
+        use $crate::matchers::unordered_elements_are_matcher::internal::{
+            UnorderedElementsAreMatcher, Requirements
+        };
+        UnorderedElementsAreMatcher::new([], Requirements::PerfectMatch)
+    }};
+
+    // TODO: Consider an alternative map-like syntax here similar to that used in
+    // https://crates.io/crates/maplit.
+    ($(($key_matcher:expr, $value_matcher:expr)),* $(,)?) => {{
+        use $crate::matchers::unordered_elements_are_matcher::internal::{
+            UnorderedElementsOfMapAreMatcher, Requirements
+        };
+        UnorderedElementsOfMapAreMatcher::new(
+            [$((Box::new($key_matcher), Box::new($value_matcher))),*],
+            Requirements::PerfectMatch
+        )
+    }};
+
+    ($($matcher:expr),* $(,)?) => {{
+        use $crate::matchers::unordered_elements_are_matcher::internal::{
+            UnorderedElementsAreMatcher, Requirements
+        };
+        UnorderedElementsAreMatcher::new([$(Box::new($matcher)),*], Requirements::PerfectMatch)
+    }};
+}
+
+/// Matches a container containing elements matched by the given matchers.
+///
+/// To match, each given matcher must have a corresponding element in the
+/// container which it matches. There must be a mapping uniquely matching each
+/// matcher to a container element. The container can, however, contain
+/// additional elements that don't correspond to any matcher.
+///
+/// Put another way, `contains_each!` matches if there is a subset of the actual
+/// container which [`unordered_elements_are`] would match.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(vec![3, 2, 1], contains_each![eq(2), ge(3)])?;   // Passes
+/// verify_that!(vec![3, 2, 1], contains_each![ge(2), ge(2)])?;   // Passes
+/// #     Ok(())
+/// # }
+/// # fn should_fail_1() -> Result<()> {
+/// verify_that!(vec![1], contains_each![eq(1), ge(2)])?;         // Fails: container too small
+/// #     Ok(())
+/// # }
+/// # fn should_fail_2() -> Result<()> {
+/// verify_that!(vec![3, 2, 1], contains_each![eq(1), ge(4)])?;   // Fails: second matcher unmatched
+/// #     Ok(())
+/// # }
+/// # fn should_fail_3() -> Result<()> {
+/// verify_that!(vec![3, 2, 1], contains_each![ge(3), ge(3), ge(3)])?; // Fails: no matching
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # should_fail_1().unwrap_err();
+/// # should_fail_2().unwrap_err();
+/// # should_fail_3().unwrap_err();
+/// ```
+///
+/// The actual value must be a container implementing [`IntoIterator`]. This
+/// includes standard containers, slices (when dereferenced) and arrays.
+///
+/// This can also match against [`HashMap`][std::collections::HashMap] and
+/// similar collections. The arguments are a sequence of pairs of matchers
+/// corresponding to the keys and their respective values.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # use std::collections::HashMap;
+/// let value: HashMap<u32, &'static str> =
+///     HashMap::from_iter([(1, "One"), (2, "Two"), (3, "Three")]);
+/// verify_that!(value, contains_each![(eq(2), eq("Two")), (eq(1), eq("One"))])
+/// #     .unwrap();
+/// ```
+///
+/// This matcher does not support matching directly against an [`Iterator`]. To
+/// match against an iterator, use [`Iterator::collect`] to build a [`Vec`].
+///
+/// The matcher proceeds in three stages:
+///
+/// 1. It first checks whether the actual value is large enough to possibly be
+///    matched by each of the given matchers. If not, then it immediately fails
+///    explaining that the size is too small.
+///
+/// 2. It then checks whether each matcher matches at least one corresponding
+///    element in the actual container and fails if that is not the case. The
+///    failure message indicates which matcher had no corresponding element.
+///
+/// 3. Finally, it checks whether the mapping of matchers to corresponding
+///    actual elements is 1-1 and fails if that is not the case. The failure
+///    message then shows the best matching it could find, including which
+///    matchers did not have corresponding unique elements in the container.
+///
+/// [`IntoIterator`]: std::iter::IntoIterator
+/// [`Iterator`]: std::iter::Iterator
+/// [`Iterator::collect`]: std::iter::Iterator::collect
+/// [`Vec`]: std::vec::Vec
+#[macro_export]
+macro_rules! contains_each {
+    ($(,)?) => {{
+        use $crate::matchers::unordered_elements_are_matcher::internal::{
+            UnorderedElementsAreMatcher, Requirements
+        };
+        UnorderedElementsAreMatcher::new([], Requirements::Superset)
+    }};
+
+    // TODO: Consider an alternative map-like syntax here similar to that used in
+    // https://crates.io/crates/maplit.
+    ($(($key_matcher:expr, $value_matcher:expr)),* $(,)?) => {{
+        use $crate::matchers::unordered_elements_are_matcher::internal::{
+            UnorderedElementsOfMapAreMatcher, Requirements
+        };
+        UnorderedElementsOfMapAreMatcher::new(
+            [$((Box::new($key_matcher), Box::new($value_matcher))),*],
+            Requirements::Superset
+        )
+    }};
+
+    ($($matcher:expr),* $(,)?) => {{
+        use $crate::matchers::unordered_elements_are_matcher::internal::{
+            UnorderedElementsAreMatcher, Requirements
+        };
+        UnorderedElementsAreMatcher::new([$(Box::new($matcher)),*], Requirements::Superset)
+    }}
+}
+
+/// Matches a container all of whose elements are matched by the given matchers.
+///
+/// To match, each element in the container must have a corresponding matcher
+/// which matches it. There must be a 1-1 mapping from container elements to
+/// matchers, so that no matcher has more than one corresponding element.
+///
+/// There may, however, be matchers not corresponding to any elements in the
+/// container.
+///
+/// Put another way, `is_contained_in!` matches if there is a subset of the
+/// matchers which would match with [`unordered_elements_are`].
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # fn should_pass() -> Result<()> {
+/// verify_that!(vec![2, 1], is_contained_in![eq(1), ge(2)])?;   // Passes
+/// verify_that!(vec![2, 1], is_contained_in![ge(1), ge(1)])?;   // Passes
+/// #     Ok(())
+/// # }
+/// # fn should_fail_1() -> Result<()> {
+/// verify_that!(vec![1, 2, 3], is_contained_in![eq(1), ge(2)])?; // Fails: container too large
+/// #     Ok(())
+/// # }
+/// # fn should_fail_2() -> Result<()> {
+/// verify_that!(vec![2, 1], is_contained_in![eq(1), ge(4)])?;    // Fails: second matcher unmatched
+/// #     Ok(())
+/// # }
+/// # fn should_fail_3() -> Result<()> {
+/// verify_that!(vec![3, 1], is_contained_in![ge(3), ge(3), ge(3)])?; // Fails: no matching
+/// #     Ok(())
+/// # }
+/// # should_pass().unwrap();
+/// # should_fail_1().unwrap_err();
+/// # should_fail_2().unwrap_err();
+/// # should_fail_3().unwrap_err();
+/// ```
+///
+/// The actual value must be a container implementing [`IntoIterator`]. This
+/// includes standard containers, slices (when dereferenced) and arrays.
+///
+/// This can also match against [`HashMap`][std::collections::HashMap] and
+/// similar collections. The arguments are a sequence of pairs of matchers
+/// corresponding to the keys and their respective values.
+///
+/// ```
+/// # use googletest::prelude::*;
+/// # use std::collections::HashMap;
+/// let value: HashMap<u32, &'static str> = HashMap::from_iter([(1, "One"), (2, "Two")]);
+/// verify_that!(
+///     value,
+///     is_contained_in![(eq(2), eq("Two")), (eq(1), eq("One")), (eq(3), eq("Three"))]
+/// )
+/// #     .unwrap();
+/// ```
+///
+/// This matcher does not support matching directly against an [`Iterator`]. To
+/// match against an iterator, use [`Iterator::collect`] to build a [`Vec`].
+///
+/// The matcher proceeds in three stages:
+///
+/// 1. It first checks whether the actual value is too large to possibly be
+///    matched by each of the given matchers. If so, it immediately fails
+///    explaining that the size is too large.
+///
+/// 2. It then checks whether each actual container element is matched by at
+///    least one matcher and fails if that is not the case. The failure message
+///    indicates which element had no corresponding matcher.
+///
+/// 3. Finally, it checks whether the mapping of elements to corresponding
+///    matchers is 1-1 and fails if that is not the case. The failure message
+///    then shows the best matching it could find, including which container
+///    elements did not have corresponding matchers.
+///
+/// [`IntoIterator`]: std::iter::IntoIterator
+/// [`Iterator`]: std::iter::Iterator
+/// [`Iterator::collect`]: std::iter::Iterator::collect
+/// [`Vec`]: std::vec::Vec
+#[macro_export]
+macro_rules! is_contained_in {
+    ($(,)?) => {{
+        use $crate::matchers::unordered_elements_are_matcher::internal::{
+            UnorderedElementsAreMatcher, Requirements
+        };
+        UnorderedElementsAreMatcher::new([], Requirements::Subset)
+    }};
+
+    // TODO: Consider an alternative map-like syntax here similar to that used in
+    // https://crates.io/crates/maplit.
+    ($(($key_matcher:expr, $value_matcher:expr)),* $(,)?) => {{
+        use $crate::matchers::unordered_elements_are_matcher::internal::{
+            UnorderedElementsOfMapAreMatcher, Requirements
+        };
+        UnorderedElementsOfMapAreMatcher::new(
+            [$((Box::new($key_matcher), Box::new($value_matcher))),*],
+            Requirements::Subset
+        )
+    }};
+
+    ($($matcher:expr),* $(,)?) => {{
+        use $crate::matchers::unordered_elements_are_matcher::internal::{
+            UnorderedElementsAreMatcher, Requirements
+        };
+        UnorderedElementsAreMatcher::new([$(Box::new($matcher)),*], Requirements::Subset)
+    }}
+}
+
+/// Module for use only by the macros in this module.
+///
+/// **For internal use only. API stablility is not guaranteed!**
+#[doc(hidden)]
+pub mod internal {
+    use crate::matcher::{Matcher, MatcherResult};
+    use crate::matcher_support::count_elements::count_elements;
+    use crate::matcher_support::description::Description;
+    use std::collections::HashSet;
+    use std::fmt::{Debug, Display};
+    use std::marker::PhantomData;
+
+    /// This struct is meant to be used only through the
+    /// `unordered_elements_are![...]` macro.
+    ///
+    /// **For internal use only. API stablility is not guaranteed!**
+    #[doc(hidden)]
+    pub struct UnorderedElementsAreMatcher<'a, ContainerT: ?Sized, T: Debug, const N: usize> {
+        elements: [Box<dyn Matcher<ActualT = T> + 'a>; N],
+        requirements: Requirements,
+        phantom: PhantomData<ContainerT>,
+    }
+
+    impl<'a, ContainerT: ?Sized, T: Debug, const N: usize>
+        UnorderedElementsAreMatcher<'a, ContainerT, T, N>
+    {
+        pub fn new(
+            elements: [Box<dyn Matcher<ActualT = T> + 'a>; N],
+            requirements: Requirements,
+        ) -> Self {
+            Self { elements, requirements, phantom: Default::default() }
+        }
+    }
+
+    // This matcher performs the checks in three different steps in both `matches`
+    // and `explain_match`. This is useful for performance but also to produce
+    // an actionable error message.
+    // 1. `UnorderedElementsAreMatcher` verifies that both collections have the same
+    // size
+    // 2. `UnorderedElementsAreMatcher` verifies that each actual element matches at
+    // least one expected element and vice versa.
+    // 3. `UnorderedElementsAreMatcher` verifies that a perfect matching exists
+    // using Ford-Fulkerson.
+    impl<'a, T: Debug, ContainerT: Debug + ?Sized, const N: usize> Matcher
+        for UnorderedElementsAreMatcher<'a, ContainerT, T, N>
+    where
+        for<'b> &'b ContainerT: IntoIterator<Item = &'b T>,
+    {
+        type ActualT = ContainerT;
+
+        fn matches(&self, actual: &ContainerT) -> MatcherResult {
+            let match_matrix = MatchMatrix::generate(actual, &self.elements);
+            match_matrix.is_match_for(self.requirements).into()
+        }
+
+        fn explain_match(&self, actual: &ContainerT) -> String {
+            if let Some(size_mismatch_explanation) =
+                self.requirements.explain_size_mismatch(actual, N)
+            {
+                return size_mismatch_explanation;
+            }
+
+            let match_matrix = MatchMatrix::generate(actual, &self.elements);
+            if let Some(unmatchable_explanation) =
+                match_matrix.explain_unmatchable(self.requirements)
+            {
+                return unmatchable_explanation;
+            }
+
+            let best_match = match_matrix.find_best_match();
+            best_match
+                .get_explanation(actual, &self.elements, self.requirements)
+                .unwrap_or("whose elements all match".to_string())
+        }
+
+        fn describe(&self, matcher_result: MatcherResult) -> String {
+            format!(
+                "{} elements matching in any order:\n{}",
+                if matcher_result.into() { "contains" } else { "doesn't contain" },
+                self.elements
+                    .iter()
+                    .map(|matcher| matcher.describe(MatcherResult::Match))
+                    .collect::<Description>()
+                    .enumerate()
+                    .indent()
+            )
+        }
+    }
+
+    type KeyValueMatcher<'a, KeyT, ValueT> =
+        (Box<dyn Matcher<ActualT = KeyT> + 'a>, Box<dyn Matcher<ActualT = ValueT> + 'a>);
+
+    /// This is the analogue to [UnorderedElementsAreMatcher] for maps and
+    /// map-like collections.
+    ///
+    /// **For internal use only. API stablility is not guaranteed!**
+    #[doc(hidden)]
+    pub struct UnorderedElementsOfMapAreMatcher<'a, ContainerT, KeyT, ValueT, const N: usize>
+    where
+        ContainerT: ?Sized,
+        KeyT: Debug,
+        ValueT: Debug,
+    {
+        elements: [KeyValueMatcher<'a, KeyT, ValueT>; N],
+        requirements: Requirements,
+        phantom: PhantomData<ContainerT>,
+    }
+
+    impl<'a, ContainerT, KeyT: Debug, ValueT: Debug, const N: usize>
+        UnorderedElementsOfMapAreMatcher<'a, ContainerT, KeyT, ValueT, N>
+    {
+        pub fn new(
+            elements: [KeyValueMatcher<'a, KeyT, ValueT>; N],
+            requirements: Requirements,
+        ) -> Self {
+            Self { elements, requirements, phantom: Default::default() }
+        }
+    }
+
+    impl<'a, KeyT: Debug, ValueT: Debug, ContainerT: Debug + ?Sized, const N: usize> Matcher
+        for UnorderedElementsOfMapAreMatcher<'a, ContainerT, KeyT, ValueT, N>
+    where
+        for<'b> &'b ContainerT: IntoIterator<Item = (&'b KeyT, &'b ValueT)>,
+    {
+        type ActualT = ContainerT;
+
+        fn matches(&self, actual: &ContainerT) -> MatcherResult {
+            let match_matrix = MatchMatrix::generate_for_map(actual, &self.elements);
+            match_matrix.is_match_for(self.requirements).into()
+        }
+
+        fn explain_match(&self, actual: &ContainerT) -> String {
+            if let Some(size_mismatch_explanation) =
+                self.requirements.explain_size_mismatch(actual, N)
+            {
+                return size_mismatch_explanation;
+            }
+
+            let match_matrix = MatchMatrix::generate_for_map(actual, &self.elements);
+            if let Some(unmatchable_explanation) =
+                match_matrix.explain_unmatchable(self.requirements)
+            {
+                return unmatchable_explanation;
+            }
+
+            let best_match = match_matrix.find_best_match();
+
+            best_match
+                .get_explanation_for_map(actual, &self.elements, self.requirements)
+                .unwrap_or("whose elements all match".to_string())
+        }
+
+        fn describe(&self, matcher_result: MatcherResult) -> String {
+            format!(
+                "{} elements matching in any order:\n{}",
+                if matcher_result.into() { "contains" } else { "doesn't contain" },
+                self.elements
+                    .iter()
+                    .map(|(key_matcher, value_matcher)| format!(
+                        "{} => {}",
+                        key_matcher.describe(MatcherResult::Match),
+                        value_matcher.describe(MatcherResult::Match)
+                    ))
+                    .collect::<Description>()
+                    .indent()
+            )
+        }
+    }
+
+    /// The requirements of the mapping between matchers and actual values by
+    /// which [`UnorderedElemetnsAre`] is deemed to match its input.
+    ///
+    /// **For internal use only. API stablility is not guaranteed!**
+    #[doc(hidden)]
+    #[derive(Clone, Copy)]
+    pub enum Requirements {
+        /// There must be a 1:1 correspondence between the actual values and the
+        /// matchers.
+        PerfectMatch,
+
+        /// The mapping from matched actual values to their corresponding
+        /// matchers must be surjective.
+        Superset,
+
+        /// The mapping from matchers to matched actual values must be
+        /// surjective.
+        Subset,
+    }
+
+    impl Requirements {
+        fn explain_size_mismatch<ContainerT: ?Sized>(
+            &self,
+            actual: &ContainerT,
+            expected_size: usize,
+        ) -> Option<String>
+        where
+            for<'b> &'b ContainerT: IntoIterator,
+        {
+            let actual_size = count_elements(actual);
+            match self {
+                Requirements::PerfectMatch if actual_size != expected_size => {
+                    Some(format!("which has size {} (expected {})", actual_size, expected_size))
+                }
+
+                Requirements::Superset if actual_size < expected_size => Some(format!(
+                    "which has size {} (expected at least {})",
+                    actual_size, expected_size
+                )),
+
+                Requirements::Subset if actual_size > expected_size => Some(format!(
+                    "which has size {} (expected at most {})",
+                    actual_size, expected_size
+                )),
+
+                _ => None,
+            }
+        }
+    }
+
+    impl Display for Requirements {
+        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+            match self {
+                Requirements::PerfectMatch => {
+                    write!(f, "perfect")
+                }
+                Requirements::Superset => {
+                    write!(f, "superset")
+                }
+                Requirements::Subset => {
+                    write!(f, "subset")
+                }
+            }
+        }
+    }
+
+    /// The bipartite matching graph between actual and expected elements.
+    struct MatchMatrix<const N: usize>(Vec<[MatcherResult; N]>);
+
+    impl<const N: usize> MatchMatrix<N> {
+        fn generate<'a, T: Debug + 'a, ContainerT: Debug + ?Sized>(
+            actual: &ContainerT,
+            expected: &[Box<dyn Matcher<ActualT = T> + 'a>; N],
+        ) -> Self
+        where
+            for<'b> &'b ContainerT: IntoIterator<Item = &'b T>,
+        {
+            let mut matrix = MatchMatrix(vec![[MatcherResult::NoMatch; N]; count_elements(actual)]);
+            for (actual_idx, actual) in actual.into_iter().enumerate() {
+                for (expected_idx, expected) in expected.iter().enumerate() {
+                    matrix.0[actual_idx][expected_idx] = expected.matches(actual);
+                }
+            }
+            matrix
+        }
+
+        fn generate_for_map<'a, KeyT: Debug, ValueT: Debug, ContainerT: Debug + ?Sized>(
+            actual: &ContainerT,
+            expected: &[KeyValueMatcher<'a, KeyT, ValueT>; N],
+        ) -> Self
+        where
+            for<'b> &'b ContainerT: IntoIterator<Item = (&'b KeyT, &'b ValueT)>,
+        {
+            let mut matrix = MatchMatrix(vec![[MatcherResult::NoMatch; N]; count_elements(actual)]);
+            for (actual_idx, (actual_key, actual_value)) in actual.into_iter().enumerate() {
+                for (expected_idx, (expected_key, expected_value)) in expected.iter().enumerate() {
+                    matrix.0[actual_idx][expected_idx] = (expected_key.matches(actual_key).into()
+                        && expected_value.matches(actual_value).into())
+                    .into();
+                }
+            }
+            matrix
+        }
+
+        fn is_match_for(&self, requirements: Requirements) -> bool {
+            match requirements {
+                Requirements::PerfectMatch => {
+                    !self.find_unmatchable_elements().has_unmatchable_elements()
+                        && self.find_best_match().is_full_match()
+                }
+                Requirements::Superset => {
+                    !self.find_unmatched_expected().has_unmatchable_elements()
+                        && self.find_best_match().is_superset_match()
+                }
+                Requirements::Subset => {
+                    !self.find_unmatched_actual().has_unmatchable_elements()
+                        && self.find_best_match().is_subset_match()
+                }
+            }
+        }
+
+        fn explain_unmatchable(&self, requirements: Requirements) -> Option<String> {
+            let unmatchable_elements = match requirements {
+                Requirements::PerfectMatch => self.find_unmatchable_elements(),
+                Requirements::Superset => self.find_unmatched_expected(),
+                Requirements::Subset => self.find_unmatched_actual(),
+            };
+            unmatchable_elements.get_explanation()
+        }
+
+        // Verifies that each actual matches at least one expected and that
+        // each expected matches at least one actual.
+        // This is a necessary condition but not sufficient. But it is faster
+        // than `find_best_match()`.
+        fn find_unmatchable_elements(&self) -> UnmatchableElements<N> {
+            let unmatchable_actual =
+                self.0.iter().map(|row| row.iter().all(|&e| e.is_no_match())).collect();
+            let mut unmatchable_expected = [false; N];
+            for (col_idx, expected) in unmatchable_expected.iter_mut().enumerate() {
+                *expected = self.0.iter().map(|row| row[col_idx]).all(|e| e.is_no_match());
+            }
+            UnmatchableElements { unmatchable_actual, unmatchable_expected }
+        }
+
+        fn find_unmatched_expected(&self) -> UnmatchableElements<N> {
+            let mut unmatchable_expected = [false; N];
+            for (col_idx, expected) in unmatchable_expected.iter_mut().enumerate() {
+                *expected = self.0.iter().map(|row| row[col_idx]).all(|e| e.is_no_match());
+            }
+            UnmatchableElements { unmatchable_actual: vec![false; N], unmatchable_expected }
+        }
+
+        fn find_unmatched_actual(&self) -> UnmatchableElements<N> {
+            let unmatchable_actual =
+                self.0.iter().map(|row| row.iter().all(|e| e.is_no_match())).collect();
+            UnmatchableElements { unmatchable_actual, unmatchable_expected: [false; N] }
+        }
+
+        // Verifies that a full match exists.
+        //
+        // Uses the well-known Ford-Fulkerson max flow method to find a maximum
+        // bipartite matching. Flow is considered to be from actual to expected.
+        // There is an implicit source node that is connected to all of the actual
+        // nodes, and an implicit sink node that is connected to all of the
+        // expected nodes. All edges have unit capacity.
+        //
+        // Neither the flow graph nor the residual flow graph are represented
+        // explicitly. Instead, they are implied by the information in `self.0` and
+        // the local `actual_match : [Option<usize>; N]` whose elements are initialized
+        // to `None`. This represents the initial state of the algorithm,
+        // where the flow graph is empty, and the residual flow graph has the
+        // following edges:
+        //   - An edge from source to each actual element node
+        //   - An edge from each expected element node to sink
+        //   - An edge from each actual element node to each expected element node, if
+        //     the actual element matches the expected element, i.e.
+        //     `matches!(self.0[actual_id][expected_id], Matches)`
+        //
+        // When the `try_augment(...)` method adds a flow, it sets `actual_match[l] =
+        // Some(r)` for some nodes l and r. This induces the following changes:
+        //   - The edges (source, l), (l, r), and (r, sink) are added to the flow graph.
+        //   - The same three edges are removed from the residual flow graph.
+        //   - The reverse edges (l, source), (r, l), and (sink, r) are added to the
+        //     residual flow graph, which is a directional graph representing unused
+        //     flow capacity.
+        //
+        // When the method augments a flow (changing `actual_match[l]` from `Some(r1)`
+        // to `Some(r2)`), this can be thought of as "undoing" the above steps
+        // with respect to r1 and "redoing" them with respect to r2.
+        //
+        // It bears repeating that the flow graph and residual flow graph are
+        // never represented explicitly, but can be derived by looking at the
+        // information in 'self.0' and in `actual_match`.
+        //
+        // As an optimization, there is a second local `expected_match: [Option<usize>;
+        // N]` which does not provide any new information. Instead, it enables
+        // more efficient queries about edges entering or leaving the expected elements
+        // nodes of the flow or residual flow graphs. The following invariants
+        // are maintained:
+        //
+        // actual_match[a] == None or expected_match[actual_match[a].unwrap()] ==
+        // Some(a)
+        // expected_match[r] == None or actual_match[expected_match[e].unwrap()] ==
+        // Some(e)
+        //
+        // | [ source ]                                                              |
+        // |   |||                                                                   |
+        // |   |||                                                                   |
+        // |   ||\-> actual_match[0]=Some(1) -\   expected_match[0]=None    ---\     |
+        // |   ||                             |                                |     |
+        // |   |\--> actual_match[1]=None     \-> expected_match[1]=Some(0) --\|     |
+        // |   |                                                              ||     |
+        // |   \---> actual_match[2]=Some(2)  --> expected_match[2]=Some(2) -\||     |
+        // |                                                                 |||     |
+        // |         elements                     matchers                   vvv     |
+        // |                                                               [ sink ]  |
+        //
+        // See Also:
+        //   [1] Cormen, et al (2001). "Section 26.2: The Ford-Fulkerson method".
+        //       "Introduction to Algorithms (Second ed.)", pp. 651-664.
+        //   [2] "Ford-Fulkerson algorithm", Wikipedia,
+        //       'http://en.wikipedia.org/wiki/Ford%E2%80%93Fulkerson_algorithm'
+        fn find_best_match(&self) -> BestMatch<N> {
+            let mut actual_match = vec![None; self.0.len()];
+            let mut expected_match: [Option<usize>; N] = [None; N];
+            // Searches the residual flow graph for a path from each actual node to
+            // the sink in the residual flow graph, and if one is found, add this path
+            // to the graph.
+            // It's okay to search through the actual nodes once. The
+            // edge from the implicit source node to each previously-visited actual
+            // node will have flow if that actual node has any path to the sink
+            // whatsoever. Subsequent augmentations can only add flow to the
+            // network, and cannot take away that previous flow unit from the source.
+            // Since the source-to-actual edge can only carry one flow unit (or,
+            // each actual element can be matched to only one expected element), there is no
+            // need to visit the actual nodes more than once looking for
+            // augmented paths. The flow is known to be possible or impossible
+            // by looking at the node once.
+            for actual_idx in 0..self.0.len() {
+                assert!(actual_match[actual_idx].is_none());
+                let mut seen = [false; N];
+                self.try_augment(actual_idx, &mut seen, &mut actual_match, &mut expected_match);
+            }
+            BestMatch(actual_match)
+        }
+
+        // Perform a depth-first search from actual node `actual_idx` to the sink by
+        // searching for an unassigned expected node. If a path is found, flow
+        // is added to the network by linking the actual and expected vector elements
+        // corresponding each segment of the path. Returns true if a path to
+        // sink was found, which means that a unit of flow was added to the
+        // network. The 'seen' array elements correspond to expected nodes and are
+        // marked to eliminate cycles from the search.
+        //
+        // Actual nodes will only be explored at most once because they
+        // are accessible from at most one expected node in the residual flow
+        // graph.
+        //
+        // Note that `actual_match[actual_idx]` is the only element of `actual_match`
+        // that `try_augment(...)` will potentially transition from `None` to
+        // `Some(...)`. Any other `actual_match` element holding `None` before
+        // `try_augment(...)` will be holding it when `try_augment(...)`
+        // returns.
+        //
+        fn try_augment(
+            &self,
+            actual_idx: usize,
+            seen: &mut [bool; N],
+            actual_match: &mut [Option<usize>],
+            expected_match: &mut [Option<usize>; N],
+        ) -> bool {
+            for expected_idx in 0..N {
+                if seen[expected_idx] {
+                    continue;
+                }
+                if self.0[actual_idx][expected_idx].is_no_match() {
+                    continue;
+                }
+                // There is an edge between `actual_idx` and `expected_idx`.
+                seen[expected_idx] = true;
+                // Next a search is performed to determine whether
+                // this edge is a dead end or leads to the sink.
+                //
+                // `expected_match[expected_idx].is_none()` means that there is residual flow
+                // from expected node at index expected_idx to the sink, so we
+                // can use that to finish this flow path and return success.
+                //
+                // Otherwise, we look for a residual flow starting from
+                // `expected_match[expected_idx].unwrap()` by calling
+                // ourselves recursively to see if this ultimately leads to
+                // sink.
+                if expected_match[expected_idx].is_none()
+                    || self.try_augment(
+                        expected_match[expected_idx].unwrap(),
+                        seen,
+                        actual_match,
+                        expected_match,
+                    )
+                {
+                    // We found a residual flow from source to sink. We thus need to add the new
+                    // edge to the current flow.
+                    // Note: this also remove the potential flow that existed by overwriting the
+                    // value in the `expected_match` and `actual_match`.
+                    expected_match[expected_idx] = Some(actual_idx);
+                    actual_match[actual_idx] = Some(expected_idx);
+                    return true;
+                }
+            }
+            false
+        }
+    }
+
+    /// The list of elements that do not match any element in the corresponding
+    /// set.
+    /// These lists are represented as fixed sized bit set to avoid
+    /// allocation.
+    /// TODO(bjacotg) Use BitArr!(for N) once generic_const_exprs is stable.
+    struct UnmatchableElements<const N: usize> {
+        unmatchable_actual: Vec<bool>,
+        unmatchable_expected: [bool; N],
+    }
+
+    impl<const N: usize> UnmatchableElements<N> {
+        fn has_unmatchable_elements(&self) -> bool {
+            self.unmatchable_actual.iter().any(|b| *b)
+                || self.unmatchable_expected.iter().any(|b| *b)
+        }
+
+        fn get_explanation(&self) -> Option<String> {
+            let unmatchable_actual = self.unmatchable_actual();
+            let actual_idx = unmatchable_actual
+                .iter()
+                .map(|idx| format!("#{}", idx))
+                .collect::<Vec<_>>()
+                .join(", ");
+            let unmatchable_expected = self.unmatchable_expected();
+            let expected_idx = unmatchable_expected
+                .iter()
+                .map(|idx| format!("#{}", idx))
+                .collect::<Vec<_>>()
+                .join(", ");
+            match (unmatchable_actual.len(), unmatchable_expected.len()) {
+                (0, 0) => None,
+                (1, 0) => {
+                    Some(format!("whose element {actual_idx} does not match any expected elements"))
+                }
+                (_, 0) => {
+                    Some(format!("whose elements {actual_idx} do not match any expected elements",))
+                }
+                (0, 1) => Some(format!(
+                    "which has no element matching the expected element {expected_idx}"
+                )),
+                (0, _) => Some(format!(
+                    "which has no elements matching the expected elements {expected_idx}"
+                )),
+                (1, 1) => Some(format!(
+                    "whose element {actual_idx} does not match any expected elements and no elements match the expected element {expected_idx}"
+                )),
+                (_, 1) => Some(format!(
+                    "whose elements {actual_idx} do not match any expected elements and no elements match the expected element {expected_idx}"
+                )),
+                (1, _) => Some(format!(
+                    "whose element {actual_idx} does not match any expected elements and no elements match the expected elements {expected_idx}"
+                )),
+                (_, _) => Some(format!(
+                    "whose elements {actual_idx} do not match any expected elements and no elements match the expected elements {expected_idx}"
+                )),
+            }
+        }
+
+        fn unmatchable_actual(&self) -> Vec<usize> {
+            self.unmatchable_actual
+                .iter()
+                .enumerate()
+                .filter_map(|(idx, b)| if *b { Some(idx) } else { None })
+                .collect()
+        }
+
+        fn unmatchable_expected(&self) -> Vec<usize> {
+            self.unmatchable_expected
+                .iter()
+                .enumerate()
+                .filter_map(|(idx, b)| if *b { Some(idx) } else { None })
+                .collect()
+        }
+    }
+
+    /// The representation of a match between actual and expected.
+    /// The value at idx represents to which expected the actual at idx is
+    /// matched with. For example, `BestMatch([Some(0), None, Some(1)])`
+    /// means:
+    ///  * The 0th element in actual matches the 0th element in expected.
+    ///  * The 1st element in actual does not match.
+    ///  * The 2nd element in actual matches the 1st element in expected.
+    struct BestMatch<const N: usize>(Vec<Option<usize>>);
+
+    impl<const N: usize> BestMatch<N> {
+        fn is_full_match(&self) -> bool {
+            self.0.iter().all(|o| o.is_some())
+        }
+
+        fn is_subset_match(&self) -> bool {
+            self.is_full_match()
+        }
+
+        fn is_superset_match(&self) -> bool {
+            self.get_unmatched_expected().is_empty()
+        }
+
+        fn get_matches(&self) -> impl Iterator<Item = (usize, usize)> + '_ {
+            self.0.iter().enumerate().filter_map(|(actual_idx, maybe_expected_idx)| {
+                maybe_expected_idx.map(|expected_idx| (actual_idx, expected_idx))
+            })
+        }
+
+        fn get_unmatched_actual(&self) -> impl Iterator<Item = usize> + '_ {
+            self.0
+                .iter()
+                .enumerate()
+                .filter(|&(_, o)| o.is_none())
+                .map(|(actual_idx, _)| actual_idx)
+        }
+
+        fn get_unmatched_expected(&self) -> Vec<usize> {
+            let matched_expected: HashSet<_> = self.0.iter().flatten().collect();
+            (0..N).filter(|expected_idx| !matched_expected.contains(expected_idx)).collect()
+        }
+
+        fn get_explanation<'a, T: Debug, ContainerT: Debug + ?Sized>(
+            &self,
+            actual: &ContainerT,
+            expected: &[Box<dyn Matcher<ActualT = T> + 'a>; N],
+            requirements: Requirements,
+        ) -> Option<String>
+        where
+            for<'b> &'b ContainerT: IntoIterator<Item = &'b T>,
+        {
+            let actual: Vec<_> = actual.into_iter().collect();
+            if self.is_full_match() {
+                return None;
+            }
+            let mut error_message =
+                format!("which does not have a {requirements} match with the expected elements.");
+
+            error_message.push_str("\n  The best match found was: ");
+
+            let matches = self.get_matches().map(|(actual_idx, expected_idx)|{
+                format!(
+                    "Actual element {:?} at index {actual_idx} matched expected element `{}` at index {expected_idx}.",
+                    actual[actual_idx],
+                    expected[expected_idx].describe(MatcherResult::Match),
+            )});
+
+            let unmatched_actual = self.get_unmatched_actual().map(|actual_idx| {
+                format!(
+                    "Actual element {:#?} at index {actual_idx} did not match any remaining expected element.",
+                    actual[actual_idx]
+                )
+            });
+
+            let unmatched_expected = self.get_unmatched_expected().into_iter().map(|expected_idx|{format!(
+                "Expected element `{}` at index {expected_idx} did not match any remaining actual element.",
+                expected[expected_idx].describe(MatcherResult::Match)
+            )});
+
+            let best_match = matches
+                .chain(unmatched_actual)
+                .chain(unmatched_expected)
+                .collect::<Description>()
+                .indent();
+            Some(format!(
+                "which does not have a {requirements} match with the expected elements. The best match found was:\n{best_match}"
+            ))
+        }
+
+        fn get_explanation_for_map<'a, KeyT: Debug, ValueT: Debug, ContainerT: Debug + ?Sized>(
+            &self,
+            actual: &ContainerT,
+            expected: &[KeyValueMatcher<'a, KeyT, ValueT>; N],
+            requirements: Requirements,
+        ) -> Option<String>
+        where
+            for<'b> &'b ContainerT: IntoIterator<Item = (&'b KeyT, &'b ValueT)>,
+        {
+            let actual: Vec<_> = actual.into_iter().collect();
+            if self.is_full_match() {
+                return None;
+            }
+            let mut error_message =
+                format!("which does not have a {requirements} match with the expected elements.");
+
+            error_message.push_str("\n  The best match found was: ");
+
+            let matches = self.get_matches()
+                .map(|(actual_idx, expected_idx)| {
+                    format!(
+                        "Actual element {:?} => {:?} at index {actual_idx} matched expected element `{}` => `{}` at index {expected_idx}.",
+                        actual[actual_idx].0,
+                        actual[actual_idx].1,
+                        expected[expected_idx].0.describe(MatcherResult::Match),
+                        expected[expected_idx].1.describe(MatcherResult::Match),
+                    )
+                });
+
+            let unmatched_actual = self.get_unmatched_actual()
+                .map(|actual_idx| {
+                    format!(
+                        "Actual element {:#?} => {:#?} at index {actual_idx} did not match any remaining expected element.",
+                        actual[actual_idx].0,
+                        actual[actual_idx].1,
+                    )
+                });
+
+            let unmatched_expected = self.get_unmatched_expected()
+                .into_iter()
+                .map(|expected_idx| {
+                    format!(
+                        "Expected element `{}` => `{}` at index {expected_idx} did not match any remaining actual element.",
+                        expected[expected_idx].0.describe(MatcherResult::Match),
+                        expected[expected_idx].1.describe(MatcherResult::Match),
+                    )
+                });
+
+            let best_match = matches
+                .chain(unmatched_actual)
+                .chain(unmatched_expected)
+                .collect::<Description>()
+                .indent();
+            Some(format!(
+                "which does not have a {requirements} match with the expected elements. The best match found was:\n{best_match}"
+            ))
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::internal::UnorderedElementsOfMapAreMatcher;
+    use crate::matcher::{Matcher, MatcherResult};
+    use crate::prelude::*;
+    use indoc::indoc;
+    use std::collections::HashMap;
+
+    #[test]
+    fn has_correct_description_for_map() -> Result<()> {
+        // UnorderedElementsAreMatcher maintains references to the matchers, so the
+        // constituent matchers must live longer. Inside a verify_that! macro, the
+        // compiler takes care of that, but when the matcher is created separately,
+        // we must create the constitute matchers separately so that they
+        // aren't dropped too early.
+        let matchers = ((eq(2), eq("Two")), (eq(1), eq("One")), (eq(3), eq("Three")));
+        let matcher: UnorderedElementsOfMapAreMatcher<HashMap<i32, &str>, _, _, 3> = unordered_elements_are![
+            (matchers.0.0, matchers.0.1),
+            (matchers.1.0, matchers.1.1),
+            (matchers.2.0, matchers.2.1)
+        ];
+        verify_that!(
+            Matcher::describe(&matcher, MatcherResult::Match),
+            eq(indoc!(
+                "
+                contains elements matching in any order:
+                  is equal to 2 => is equal to \"Two\"
+                  is equal to 1 => is equal to \"One\"
+                  is equal to 3 => is equal to \"Three\""
+            ))
+        )
+    }
+
+    #[test]
+    fn unordered_elements_are_description_no_full_match_with_map() -> Result<()> {
+        // UnorderedElementsAreMatcher maintains references to the matchers, so the
+        // constituent matchers must live longer. Inside a verify_that! macro, the
+        // compiler takes care of that, but when the matcher is created separately,
+        // we must create the constitute matchers separately so that they
+        // aren't dropped too early.
+        let matchers = ((anything(), eq(1)), (anything(), eq(2)), (anything(), eq(2)));
+        let matcher: UnorderedElementsOfMapAreMatcher<HashMap<u32, u32>, _, _, 3> = unordered_elements_are![
+            (matchers.0.0, matchers.0.1),
+            (matchers.1.0, matchers.1.1),
+            (matchers.2.0, matchers.2.1),
+        ];
+        let value: HashMap<u32, u32> = HashMap::from_iter([(0, 1), (1, 1), (2, 2)]);
+        verify_that!(
+            matcher.explain_match(&value),
+            displays_as(contains_regex(
+                "Actual element 2 => 2 at index [0-2] matched expected element `is anything` => `is equal to 2` at index [0-2]."
+            )).and(displays_as(contains_regex(
+                "Actual element [0-1] => [0-1] at index [0-2] did not match any remaining expected element."
+            ))).and(displays_as(contains_substring(
+                "Expected element `is anything` => `is equal to 2` at index 2 did not match any remaining actual element."
+            )))
+        )
+    }
+}
diff --git a/tests/all_matcher_test.rs b/tests/all_matcher_test.rs
new file mode 100644
index 0000000..6d7e3f8
--- /dev/null
+++ b/tests/all_matcher_test.rs
@@ -0,0 +1,93 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use googletest::matcher::Matcher;
+use googletest::prelude::*;
+use indoc::indoc;
+
+#[test]
+fn matches_any_value_when_list_is_empty() -> Result<()> {
+    verify_that!((), all!())
+}
+
+#[test]
+fn matches_value_with_single_matching_component() -> Result<()> {
+    verify_that!(123, all!(eq(123)))
+}
+
+#[test]
+fn does_not_match_value_with_single_non_matching_component() -> Result<()> {
+    verify_that!(123, not(all!(eq(456))))
+}
+
+#[test]
+fn matches_value_with_two_matching_components() -> Result<()> {
+    verify_that!("A string", all!(starts_with("A"), ends_with("string")))
+}
+
+#[test]
+fn does_not_match_value_with_one_non_matching_component_among_two_components() -> Result<()> {
+    verify_that!(123, not(all!(eq(123), eq(456))))
+}
+
+#[test]
+fn supports_trailing_comma() -> Result<()> {
+    verify_that!(
+        "An important string",
+        all!(starts_with("An"), contains_substring("important"), ends_with("string"),)
+    )
+}
+
+#[test]
+fn admits_matchers_without_static_lifetime() -> Result<()> {
+    #[derive(Debug, PartialEq)]
+    struct AStruct(i32);
+    let expected_value = AStruct(123);
+    verify_that!(AStruct(123), all![eq_deref_of(&expected_value)])
+}
+
+#[test]
+fn mismatch_description_two_failed_matchers() -> Result<()> {
+    verify_that!(
+        all!(starts_with("One"), starts_with("Two")).explain_match("Three"),
+        displays_as(eq(
+            "* which does not start with \"One\"\n  * which does not start with \"Two\""
+        ))
+    )
+}
+
+#[test]
+fn mismatch_description_empty_matcher() -> Result<()> {
+    verify_that!(all!().explain_match("Three"), displays_as(eq("which is anything")))
+}
+
+#[test]
+fn all_multiple_failed_assertions() -> Result<()> {
+    let result = verify_that!(4, all![eq(1), eq(2), eq(3)]);
+    verify_that!(
+        result,
+        err(displays_as(contains_substring(indoc!(
+            "
+            Value of: 4
+            Expected: has all the following properties:
+              * is equal to 1
+              * is equal to 2
+              * is equal to 3
+            Actual: 4,
+              * which isn't equal to 1
+              * which isn't equal to 2
+              * which isn't equal to 3"
+        ))))
+    )
+}
diff --git a/tests/any_matcher_test.rs b/tests/any_matcher_test.rs
new file mode 100644
index 0000000..1bdd794
--- /dev/null
+++ b/tests/any_matcher_test.rs
@@ -0,0 +1,93 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use googletest::matcher::Matcher;
+use googletest::prelude::*;
+use indoc::indoc;
+
+#[test]
+fn does_not_match_value_when_list_is_empty() -> Result<()> {
+    verify_that!((), not(any!()))
+}
+
+#[test]
+fn matches_value_with_single_matching_component() -> Result<()> {
+    verify_that!(123, any!(eq(123)))
+}
+
+#[test]
+fn does_not_match_value_with_single_non_matching_component() -> Result<()> {
+    verify_that!(123, not(any!(eq(456))))
+}
+
+#[test]
+fn matches_value_with_first_of_two_matching_components() -> Result<()> {
+    verify_that!("A string", any!(starts_with("A"), starts_with("string")))
+}
+
+#[test]
+fn matches_value_with_second_of_two_matching_components() -> Result<()> {
+    verify_that!("A string", any!(starts_with("string"), starts_with("A")))
+}
+
+#[test]
+fn supports_trailing_comma() -> Result<()> {
+    verify_that!(
+        "An important string",
+        any!(starts_with("An"), contains_substring("important"), ends_with("string"),)
+    )
+}
+
+#[test]
+fn admits_matchers_without_static_lifetime() -> Result<()> {
+    #[derive(Debug, PartialEq)]
+    struct AStruct(i32);
+    let expected_value = AStruct(123);
+    verify_that!(AStruct(123), any![eq_deref_of(&expected_value)])
+}
+
+#[test]
+fn mismatch_description_two_failed_matchers() -> Result<()> {
+    verify_that!(
+        any!(starts_with("One"), starts_with("Two")).explain_match("Three"),
+        displays_as(eq(
+            "* which does not start with \"One\"\n  * which does not start with \"Two\""
+        ))
+    )
+}
+
+#[test]
+fn mismatch_description_empty_matcher() -> Result<()> {
+    verify_that!(any!().explain_match("Three"), displays_as(eq("which never matches")))
+}
+
+#[test]
+fn all_multiple_failed_assertions() -> Result<()> {
+    let result = verify_that!(4, any![eq(1), eq(2), eq(3)]);
+    verify_that!(
+        result,
+        err(displays_as(contains_substring(indoc!(
+            "
+            Value of: 4
+            Expected: has at least one of the following properties:
+              * is equal to 1
+              * is equal to 2
+              * is equal to 3
+            Actual: 4,
+              * which isn't equal to 1
+              * which isn't equal to 2
+              * which isn't equal to 3"
+        ))))
+    )
+}
diff --git a/tests/colorized_diff_test.rs b/tests/colorized_diff_test.rs
new file mode 100644
index 0000000..d1b4ecb
--- /dev/null
+++ b/tests/colorized_diff_test.rs
@@ -0,0 +1,52 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use googletest::prelude::*;
+use indoc::indoc;
+use std::fmt::{Display, Write};
+
+// Make a long text with each element of the iterator on one line.
+// `collection` must contains at least one element.
+fn build_text<T: Display>(mut collection: impl Iterator<Item = T>) -> String {
+    let mut text = String::new();
+    write!(&mut text, "{}", collection.next().expect("Provided collection without elements"))
+        .unwrap();
+    for item in collection {
+        write!(&mut text, "\n{}", item).unwrap();
+    }
+    text
+}
+
+#[test]
+fn colors_appear_when_no_color_is_no_set_and_force_color_is_set() -> Result<()> {
+    std::env::remove_var("NO_COLOR");
+    std::env::set_var("FORCE_COLOR", "1");
+
+    let result = verify_that!(build_text(1..50), eq(build_text(1..51)));
+
+    verify_that!(
+        result,
+        err(displays_as(contains_substring(indoc! {
+            "
+
+            Difference(-\x1B[1;31mactual\x1B[0m / +\x1B[1;32mexpected\x1B[0m):
+             1
+             2
+             \x1B[3m<---- 45 common lines omitted ---->\x1B[0m
+             48
+             49
+            +\x1B[1;32m50\x1B[0m"
+        })))
+    )
+}
diff --git a/tests/composition_test.rs b/tests/composition_test.rs
new file mode 100644
index 0000000..7ae146f
--- /dev/null
+++ b/tests/composition_test.rs
@@ -0,0 +1,71 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use googletest::prelude::*;
+
+#[test]
+fn all_matcher_works_as_inner_matcher() -> Result<()> {
+    let value = vec![1];
+    verify_that!(value, contains_each![all!(gt(0), lt(2))])
+}
+
+#[test]
+fn matches_pattern_works_as_inner_matcher() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct(i32);
+    verify_that!(vec![AStruct(123)], contains_each![matches_pattern!(AStruct(eq(123)))])
+}
+
+#[test]
+fn matches_pattern_works_with_property_as_inner_matcher() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct(i32);
+    impl AStruct {
+        fn get_value(&self) -> i32 {
+            self.0
+        }
+    }
+    verify_that!(
+        vec![AStruct(123)],
+        contains_each![matches_pattern!(AStruct {
+            get_value(): eq(123)
+        })]
+    )
+}
+
+#[test]
+fn contains_each_works_as_inner_matcher() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct(Vec<i32>);
+    verify_that!(AStruct(vec![123]), matches_pattern!(AStruct(contains_each![eq(123)])))
+}
+
+#[test]
+fn pointwise_works_as_inner_matcher() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct(Vec<i32>);
+    verify_that!(AStruct(vec![123]), matches_pattern!(AStruct(pointwise!(eq, [123]))))
+}
+
+#[test]
+fn elements_are_works_as_inner_matcher() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct(Vec<i32>);
+    verify_that!(AStruct(vec![123]), matches_pattern!(AStruct(elements_are![eq(123)])))
+}
+
+#[test]
+fn tuple_works_as_inner_matcher() -> Result<()> {
+    verify_that!(vec![(123,)], elements_are![(eq(123),)])
+}
diff --git a/tests/elements_are_matcher_test.rs b/tests/elements_are_matcher_test.rs
new file mode 100644
index 0000000..99e9fe7
--- /dev/null
+++ b/tests/elements_are_matcher_test.rs
@@ -0,0 +1,125 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use googletest::matcher::Matcher;
+use googletest::prelude::*;
+use indoc::indoc;
+
+#[test]
+fn elements_are_matches_vector() -> Result<()> {
+    let value = vec![1, 2, 3];
+    verify_that!(value, elements_are![eq(1), eq(2), eq(3)])
+}
+
+#[test]
+fn elements_are_matches_slice() -> Result<()> {
+    let value = vec![1, 2, 3];
+    let slice = value.as_slice();
+    verify_that!(*slice, elements_are![eq(1), eq(2), eq(3)])
+}
+
+#[test]
+fn elements_are_matches_array() -> Result<()> {
+    verify_that!([1, 2, 3], elements_are![eq(1), eq(2), eq(3)])
+}
+
+#[test]
+fn elements_are_supports_trailing_comma() -> Result<()> {
+    let value = vec![1, 2, 3];
+    verify_that!(value, elements_are![eq(1), eq(2), eq(3),])
+}
+
+#[test]
+fn elements_are_returns_no_match_when_expected_and_actual_sizes_differ() -> Result<()> {
+    let value = vec![1, 2];
+    verify_that!(value, not(elements_are![eq(1), eq(2), eq(3)]))
+}
+
+#[test]
+fn elements_are_admits_matchers_without_static_lifetime() -> Result<()> {
+    #[derive(Debug, PartialEq)]
+    struct AStruct(i32);
+    let expected_value = AStruct(123);
+    verify_that!(vec![AStruct(123)], elements_are![eq_deref_of(&expected_value)])
+}
+
+#[test]
+fn elements_are_produces_correct_failure_message() -> Result<()> {
+    let result = verify_that!(vec![1, 4, 3], elements_are![eq(1), eq(2), eq(3)]);
+    verify_that!(
+        result,
+        err(displays_as(contains_substring(indoc!(
+            "
+                Value of: vec![1, 4, 3]
+                Expected: has elements:
+                  0. is equal to 1
+                  1. is equal to 2
+                  2. is equal to 3
+                Actual: [1, 4, 3],
+                  where element #1 is 4, which isn't equal to 2"
+        ))))
+    )
+}
+
+#[test]
+fn elements_are_produces_correct_failure_message_nested() -> Result<()> {
+    let result = verify_that!(
+        vec![vec![0, 1], vec![1, 2]],
+        elements_are![elements_are![eq(1), eq(2)], elements_are![eq(2), eq(3)]]
+    );
+    verify_that!(
+        result,
+        err(displays_as(contains_substring(indoc!(
+            "
+                Value of: vec![vec! [0, 1], vec! [1, 2]]
+                Expected: has elements:
+                  0. has elements:
+                       0. is equal to 1
+                       1. is equal to 2
+                  1. has elements:
+                       0. is equal to 2
+                       1. is equal to 3
+                Actual: [[0, 1], [1, 2]],
+                  where:
+                  * element #0 is [0, 1], where:
+                      * element #0 is 0, which isn't equal to 1
+                      * element #1 is 1, which isn't equal to 2
+                  * element #1 is [1, 2], where:
+                      * element #0 is 1, which isn't equal to 2
+                      * element #1 is 2, which isn't equal to 3"
+        ))))
+    )
+}
+
+#[test]
+fn elements_are_explain_match_wrong_size() -> Result<()> {
+    verify_that!(
+        elements_are![eq(1)].explain_match(&vec![1, 2]),
+        displays_as(eq("whose size is 2"))
+    )
+}
+
+fn create_matcher() -> impl Matcher<ActualT = Vec<i32>> {
+    elements_are![eq(1)]
+}
+
+#[test]
+fn elements_are_works_when_matcher_is_created_in_subroutine() -> Result<()> {
+    verify_that!(vec![1], create_matcher())
+}
+
+#[test]
+fn elements_are_implicitly_called() -> Result<()> {
+    verify_that!(vec![1, 2, 3], [eq(1), eq(2), eq(3)])
+}
diff --git a/tests/field_matcher_test.rs b/tests/field_matcher_test.rs
new file mode 100644
index 0000000..f5d85c5
--- /dev/null
+++ b/tests/field_matcher_test.rs
@@ -0,0 +1,178 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use googletest::matcher::{Matcher, MatcherResult};
+use googletest::prelude::*;
+
+#[derive(Debug)]
+struct IntField {
+    int: i32,
+}
+
+#[test]
+fn field_matches_integer_field() -> Result<()> {
+    verify_that!(IntField { int: 32 }, field!(IntField.int, eq(32)))
+}
+
+#[derive(Debug)]
+struct StringField {
+    strink: String,
+}
+
+#[test]
+fn field_matches_string_field() -> Result<()> {
+    verify_that!(StringField { strink: "yes".to_string() }, field!(StringField.strink, eq("yes")))
+}
+
+#[test]
+fn field_error_message_shows_field_name_and_inner_matcher() -> Result<()> {
+    let matcher = field!(IntField.int, eq(31));
+
+    verify_that!(
+        matcher.describe(MatcherResult::Match),
+        eq("has field `int`, which is equal to 31")
+    )
+}
+
+mod sub {
+    #[derive(Debug)]
+    pub struct SubStruct {
+        pub field: i32,
+    }
+}
+
+#[test]
+fn struct_in_other_module_matches() -> Result<()> {
+    verify_that!(sub::SubStruct { field: 32 }, field!(sub::SubStruct.field, eq(32)))
+}
+
+#[derive(Debug)]
+struct Tuple(i32, String);
+
+#[test]
+fn tuple_matches_with_index() -> Result<()> {
+    verify_that!(Tuple(32, "yes".to_string()), field!(Tuple.0, eq(32)))
+}
+
+#[test]
+fn matches_enum_value() -> Result<()> {
+    #[derive(Debug)]
+    enum AnEnum {
+        AValue(u32),
+    }
+    let value = AnEnum::AValue(123);
+
+    verify_that!(value, field!(AnEnum::AValue.0, eq(123)))
+}
+
+#[test]
+fn shows_correct_failure_message_for_wrong_struct_entry() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        a: Vec<u32>,
+    }
+    let value = AStruct { a: vec![1] };
+
+    let result = verify_that!(value, field!(AStruct.a, container_eq([])));
+
+    verify_that!(
+        result,
+        err(displays_as(contains_substring(
+            "which has field `a`, which contains the unexpected element 1"
+        )))
+    )
+}
+
+#[test]
+fn does_not_match_enum_value_with_wrong_enum_variant() -> Result<()> {
+    #[derive(Debug)]
+    enum AnEnum {
+        #[allow(dead_code)] // This variant is intentionally unused.
+        AValue(u32),
+        AnotherValue,
+    }
+    let value = AnEnum::AnotherValue;
+
+    verify_that!(value, not(field!(AnEnum::AValue.0, eq(123))))
+}
+
+#[test]
+fn shows_correct_failure_message_for_wrong_enum_value() -> Result<()> {
+    #[derive(Debug)]
+    enum AnEnum {
+        #[allow(dead_code)] // This variant is intentionally unused.
+        AValue {
+            a: u32,
+        },
+        AnotherValue,
+    }
+    let value = AnEnum::AnotherValue;
+
+    let result = verify_that!(value, field!(AnEnum::AValue.a, eq(123)));
+
+    verify_that!(
+        result,
+        err(displays_as(contains_substring("which has the wrong enum variant `AnotherValue`")))
+    )
+}
+
+#[test]
+fn shows_correct_failure_message_for_wrong_enum_value_with_tuple_field() -> Result<()> {
+    #[derive(Debug)]
+    enum AnEnum {
+        #[allow(dead_code)] // This variant is intentionally unused.
+        AValue(u32),
+        AnotherValue(u32),
+    }
+    let value = AnEnum::AnotherValue(123);
+
+    let result = verify_that!(value, field!(AnEnum::AValue.0, eq(123)));
+
+    verify_that!(
+        result,
+        err(displays_as(contains_substring("which has the wrong enum variant `AnotherValue`")))
+    )
+}
+
+#[test]
+fn shows_correct_failure_message_for_wrong_enum_value_with_named_field() -> Result<()> {
+    #[derive(Debug)]
+    enum AnEnum {
+        #[allow(dead_code)] // This variant is intentionally unused.
+        AValue(u32),
+        AnotherValue {
+            #[allow(unused)]
+            a: u32,
+        },
+    }
+    let value = AnEnum::AnotherValue { a: 123 };
+
+    let result = verify_that!(value, field!(AnEnum::AValue.0, eq(123)));
+
+    verify_that!(
+        result,
+        err(displays_as(contains_substring("which has the wrong enum variant `AnotherValue`")))
+    )
+}
+
+#[test]
+fn matches_struct_like_enum_value() -> Result<()> {
+    #[derive(Debug)]
+    enum AnEnum {
+        AValue { a_field: u32 },
+    }
+    let value = AnEnum::AValue { a_field: 123 };
+
+    verify_that!(value, field!(AnEnum::AValue.a_field, eq(123)))
+}
diff --git a/tests/lib.rs b/tests/lib.rs
new file mode 100644
index 0000000..fb243ca
--- /dev/null
+++ b/tests/lib.rs
@@ -0,0 +1,27 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+mod all_matcher_test;
+mod any_matcher_test;
+mod colorized_diff_test;
+mod composition_test;
+mod elements_are_matcher_test;
+mod field_matcher_test;
+mod matches_pattern_test;
+mod pointwise_matcher_test;
+mod property_matcher_test;
+#[cfg(feature = "proptest")]
+mod proptest_integration_test;
+mod tuple_matcher_test;
+mod unordered_elements_are_matcher_test;
diff --git a/tests/matches_pattern_test.rs b/tests/matches_pattern_test.rs
new file mode 100644
index 0000000..4904cf5
--- /dev/null
+++ b/tests/matches_pattern_test.rs
@@ -0,0 +1,1602 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use googletest::prelude::*;
+use indoc::indoc;
+
+#[test]
+fn matches_struct_containing_single_field() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+    }
+    let actual = AStruct { a_field: 123 };
+
+    verify_that!(actual, matches_pattern!(AStruct { a_field: eq(123) }))
+}
+
+#[test]
+fn matches_struct_containing_two_fields() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+        another_field: u32,
+    }
+    let actual = AStruct { a_field: 123, another_field: 234 };
+
+    verify_that!(actual, matches_pattern!(AStruct { a_field: eq(123), another_field: eq(234) }))
+}
+
+#[test]
+#[rustfmt::skip]// Otherwise fmt strips the trailing comma
+fn supports_trailing_comma_with_one_field() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+    }
+    let actual = AStruct { a_field: 123 };
+
+    verify_that!(actual, matches_pattern!(AStruct {
+        a_field: eq(123), // Block reformatting
+    }))
+}
+
+#[test]
+fn supports_trailing_comma_with_two_fields() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+        another_field: u32,
+    }
+    let actual = AStruct { a_field: 123, another_field: 234 };
+
+    verify_that!(
+        actual,
+        matches_pattern!(AStruct {
+            a_field: eq(123),
+            another_field: eq(234), // Block reformatting
+        })
+    )
+}
+
+#[test]
+fn supports_trailing_comma_with_three_fields() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+        another_field: u32,
+        a_third_field: u32,
+    }
+    let actual = AStruct { a_field: 123, another_field: 234, a_third_field: 345 };
+
+    verify_that!(
+        actual,
+        matches_pattern!(AStruct {
+            a_field: eq(123),
+            another_field: eq(234),
+            a_third_field: eq(345),
+        })
+    )
+}
+
+#[test]
+fn matches_struct_containing_nested_struct_with_field() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        a_nested_struct: ANestedStruct,
+    }
+    #[derive(Debug)]
+    struct ANestedStruct {
+        a_field: u32,
+    }
+    let actual = AStruct { a_nested_struct: ANestedStruct { a_field: 123 } };
+
+    verify_that!(
+        actual,
+        matches_pattern!(AStruct { a_nested_struct: pat!(ANestedStruct { a_field: eq(123) }) })
+    )
+}
+
+#[test]
+fn has_correct_assertion_failure_message_for_single_field() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+    }
+    let actual = AStruct { a_field: 123 };
+    let result = verify_that!(actual, matches_pattern!(AStruct { a_field: eq(234) }));
+
+    verify_that!(
+        result,
+        err(displays_as(contains_substring(indoc! {"
+            Value of: actual
+            Expected: is AStruct which has field `a_field`, which is equal to 234
+            Actual: AStruct { a_field: 123 },
+              which has field `a_field`, which isn't equal to 234
+            "
+        })))
+    )
+}
+
+#[test]
+fn has_correct_assertion_failure_message_for_two_fields() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+        another_field: u32,
+    }
+    let actual = AStruct { a_field: 123, another_field: 234 };
+    let result = verify_that!(
+        actual,
+        matches_pattern!(AStruct { a_field: eq(234), another_field: eq(123) })
+    );
+    verify_that!(
+        result,
+        err(displays_as(contains_substring(indoc!(
+            "
+            Value of: actual
+            Expected: is AStruct which has all the following properties:
+              * has field `a_field`, which is equal to 234
+              * has field `another_field`, which is equal to 123
+            Actual: AStruct { a_field: 123, another_field: 234 },
+              * which has field `a_field`, which isn't equal to 234
+              * which has field `another_field`, which isn't equal to 123"
+        ))))
+    )
+}
+
+#[test]
+fn has_correct_assertion_failure_message_for_field_and_property() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+        another_field: u32,
+    }
+    impl AStruct {
+        fn get_field(&self) -> u32 {
+            self.a_field
+        }
+    }
+    let actual = AStruct { a_field: 123, another_field: 234 };
+    let result = verify_that!(
+        actual,
+        matches_pattern!(AStruct { get_field(): eq(234), another_field: eq(123) })
+    );
+    verify_that!(
+        result,
+        err(displays_as(contains_substring(indoc!(
+            "
+            Value of: actual
+            Expected: is AStruct which has all the following properties:
+              * has property `get_field ()`, which is equal to 234
+              * has field `another_field`, which is equal to 123
+            Actual: AStruct { a_field: 123, another_field: 234 },
+              * whose property `get_field ()` is `123`, which isn't equal to 234
+              * which has field `another_field`, which isn't equal to 123"
+        ))))
+    )
+}
+
+#[test]
+fn has_meaningful_assertion_failure_message_when_wrong_enum_variant_is_used() -> Result<()> {
+    #[derive(Debug)]
+    enum AnEnum {
+        A(u32),
+        #[allow(unused)]
+        B(u32),
+    }
+    let actual = AnEnum::A(123);
+    let result = verify_that!(actual, matches_pattern!(AnEnum::B(eq(123))));
+
+    verify_that!(
+        result,
+        err(displays_as(contains_substring(indoc! {"
+            Value of: actual
+            Expected: is AnEnum :: B which has field `0`, which is equal to 123
+            Actual: A(123),
+              which has the wrong enum variant `A`
+            "
+        })))
+    )
+}
+
+#[test]
+fn supports_qualified_struct_names() -> Result<()> {
+    mod a_module {
+        #[derive(Debug)]
+        pub(super) struct AStruct {
+            pub(super) a_field: u32,
+        }
+    }
+    let actual = a_module::AStruct { a_field: 123 };
+
+    verify_that!(actual, matches_pattern!(a_module::AStruct { a_field: eq(123) }))
+}
+
+#[test]
+fn matches_tuple_struct_containing_single_field() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct(u32);
+    let actual = AStruct(123);
+
+    verify_that!(actual, matches_pattern!(AStruct(eq(123))))
+}
+
+#[test]
+fn matches_tuple_struct_containing_two_fields() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct(u32, u32);
+    let actual = AStruct(123, 234);
+
+    verify_that!(actual, matches_pattern!(AStruct(eq(123), eq(234))))
+}
+
+#[test]
+fn matches_tuple_struct_containing_three_fields() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct(u32, u32, u32);
+    let actual = AStruct(123, 234, 345);
+
+    verify_that!(actual, matches_pattern!(AStruct(eq(123), eq(234), eq(345))))
+}
+
+#[test]
+fn matches_tuple_struct_containing_four_fields() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct(u32, u32, u32, u32);
+    let actual = AStruct(123, 234, 345, 456);
+
+    verify_that!(actual, matches_pattern!(AStruct(eq(123), eq(234), eq(345), eq(456))))
+}
+
+#[test]
+fn matches_tuple_struct_containing_five_fields() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct(u32, u32, u32, u32, u32);
+    let actual = AStruct(123, 234, 345, 456, 567);
+
+    verify_that!(actual, matches_pattern!(AStruct(eq(123), eq(234), eq(345), eq(456), eq(567))))
+}
+
+#[test]
+fn matches_tuple_struct_containing_six_fields() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct(u32, u32, u32, u32, u32, u32);
+    let actual = AStruct(123, 234, 345, 456, 567, 678);
+
+    verify_that!(
+        actual,
+        matches_pattern!(AStruct(eq(123), eq(234), eq(345), eq(456), eq(567), eq(678)))
+    )
+}
+
+#[test]
+fn matches_tuple_struct_containing_seven_fields() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct(u32, u32, u32, u32, u32, u32, u32);
+    let actual = AStruct(123, 234, 345, 456, 567, 678, 789);
+
+    verify_that!(
+        actual,
+        matches_pattern!(AStruct(eq(123), eq(234), eq(345), eq(456), eq(567), eq(678), eq(789)))
+    )
+}
+
+#[test]
+fn matches_tuple_struct_containing_eight_fields() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct(u32, u32, u32, u32, u32, u32, u32, u32);
+    let actual = AStruct(123, 234, 345, 456, 567, 678, 789, 890);
+
+    verify_that!(
+        actual,
+        matches_pattern!(AStruct(
+            eq(123),
+            eq(234),
+            eq(345),
+            eq(456),
+            eq(567),
+            eq(678),
+            eq(789),
+            eq(890)
+        ))
+    )
+}
+
+#[test]
+fn matches_tuple_struct_containing_nine_fields() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct(u32, u32, u32, u32, u32, u32, u32, u32, u32);
+    let actual = AStruct(123, 234, 345, 456, 567, 678, 789, 890, 901);
+
+    verify_that!(
+        actual,
+        matches_pattern!(AStruct(
+            eq(123),
+            eq(234),
+            eq(345),
+            eq(456),
+            eq(567),
+            eq(678),
+            eq(789),
+            eq(890),
+            eq(901)
+        ))
+    )
+}
+
+#[test]
+fn matches_tuple_struct_containing_ten_fields() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct(u32, u32, u32, u32, u32, u32, u32, u32, u32, u32);
+    let actual = AStruct(123, 234, 345, 456, 567, 678, 789, 890, 901, 12);
+
+    verify_that!(
+        actual,
+        matches_pattern!(AStruct(
+            eq(123),
+            eq(234),
+            eq(345),
+            eq(456),
+            eq(567),
+            eq(678),
+            eq(789),
+            eq(890),
+            eq(901),
+            eq(12)
+        ))
+    )
+}
+
+#[test]
+fn matches_tuple_struct_with_trailing_comma() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct(u32);
+    let actual = AStruct(123);
+
+    verify_that!(
+        actual,
+        matches_pattern!(AStruct(
+            eq(123), // Keep the trailing comma, block reformatting
+        ))
+    )
+}
+
+#[test]
+fn matches_tuple_struct_with_two_fields_and_trailing_comma() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct(u32, u32);
+    let actual = AStruct(123, 234);
+
+    verify_that!(
+        actual,
+        matches_pattern!(AStruct(
+            eq(123),
+            eq(234), // Keep the trailing comma, block reformatting
+        ))
+    )
+}
+
+#[test]
+fn matches_enum_without_field() -> Result<()> {
+    #[derive(Debug)]
+    enum AnEnum {
+        A,
+    }
+    let actual = AnEnum::A;
+
+    verify_that!(actual, matches_pattern!(AnEnum::A))
+}
+
+#[test]
+fn generates_correct_failure_output_when_enum_variant_without_field_is_not_matched() -> Result<()> {
+    #[derive(Debug)]
+    enum AnEnum {
+        #[allow(unused)]
+        A,
+        B,
+    }
+    let actual = AnEnum::B;
+
+    let result = verify_that!(actual, matches_pattern!(AnEnum::A));
+
+    verify_that!(result, err(displays_as(contains_substring("is not AnEnum :: A"))))
+}
+
+#[test]
+fn generates_correct_failure_output_when_enum_variant_without_field_is_matched() -> Result<()> {
+    #[derive(Debug)]
+    enum AnEnum {
+        A,
+    }
+    let actual = AnEnum::A;
+
+    let result = verify_that!(actual, not(matches_pattern!(AnEnum::A)));
+
+    verify_that!(result, err(displays_as(contains_substring("is AnEnum :: A"))))
+}
+
+#[test]
+fn matches_enum_with_field() -> Result<()> {
+    #[derive(Debug)]
+    enum AnEnum {
+        A(u32),
+    }
+    let actual = AnEnum::A(123);
+
+    verify_that!(actual, matches_pattern!(AnEnum::A(eq(123))))
+}
+
+#[test]
+fn does_not_match_wrong_enum_value() -> Result<()> {
+    #[derive(Debug)]
+    enum AnEnum {
+        #[allow(unused)]
+        A(u32),
+        B,
+    }
+    let actual = AnEnum::B;
+
+    verify_that!(actual, not(matches_pattern!(AnEnum::A(eq(123)))))
+}
+
+#[test]
+fn includes_enum_variant_in_description_with_field() -> Result<()> {
+    #[derive(Debug)]
+    enum AnEnum {
+        A(u32),
+    }
+    let actual = AnEnum::A(123);
+
+    let result = verify_that!(actual, matches_pattern!(AnEnum::A(eq(234))));
+
+    verify_that!(
+        result,
+        err(displays_as(contains_substring("Expected: is AnEnum :: A which has field `0`")))
+    )
+}
+
+#[test]
+fn includes_enum_variant_in_negative_description_with_field() -> Result<()> {
+    #[derive(Debug)]
+    enum AnEnum {
+        A(u32),
+    }
+    let actual = AnEnum::A(123);
+
+    let result = verify_that!(actual, not(matches_pattern!(AnEnum::A(eq(123)))));
+
+    verify_that!(
+        result,
+        err(displays_as(contains_substring(
+            "Expected: is not AnEnum :: A which has field `0`, which is equal to"
+        )))
+    )
+}
+
+#[test]
+fn includes_enum_variant_in_description_with_two_fields() -> Result<()> {
+    #[derive(Debug)]
+    enum AnEnum {
+        A(u32, u32),
+    }
+    let actual = AnEnum::A(123, 234);
+
+    let result = verify_that!(actual, matches_pattern!(AnEnum::A(eq(234), eq(234))));
+
+    verify_that!(
+        result,
+        err(displays_as(contains_substring(
+            "Expected: is AnEnum :: A which has all the following properties"
+        )))
+    )
+}
+
+#[test]
+fn includes_enum_variant_in_description_with_three_fields() -> Result<()> {
+    #[derive(Debug)]
+    enum AnEnum {
+        A(u32, u32, u32),
+    }
+    let actual = AnEnum::A(123, 234, 345);
+
+    let result = verify_that!(actual, matches_pattern!(AnEnum::A(eq(234), eq(234), eq(345))));
+
+    verify_that!(
+        result,
+        err(displays_as(contains_substring(
+            "Expected: is AnEnum :: A which has all the following properties"
+        )))
+    )
+}
+
+#[test]
+fn includes_enum_variant_in_description_with_named_field() -> Result<()> {
+    #[derive(Debug)]
+    enum AnEnum {
+        A { field: u32 },
+    }
+    let actual = AnEnum::A { field: 123 };
+
+    let result = verify_that!(actual, matches_pattern!(AnEnum::A { field: eq(234) }));
+
+    verify_that!(
+        result,
+        err(displays_as(contains_substring("Expected: is AnEnum :: A which has field `field`")))
+    )
+}
+
+#[test]
+fn includes_enum_variant_in_description_with_two_named_fields() -> Result<()> {
+    #[derive(Debug)]
+    enum AnEnum {
+        A { field: u32, another_field: u32 },
+    }
+    let actual = AnEnum::A { field: 123, another_field: 234 };
+
+    let result = verify_that!(
+        actual,
+        matches_pattern!(AnEnum::A { field: eq(234), another_field: eq(234) })
+    );
+
+    verify_that!(
+        result,
+        err(displays_as(contains_substring(
+            "Expected: is AnEnum :: A which has all the following properties"
+        )))
+    )
+}
+
+#[test]
+fn includes_struct_name_in_description_with_property() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        field: u32,
+    }
+    impl AStruct {
+        fn get_field(&self) -> u32 {
+            self.field
+        }
+    }
+    let actual = AStruct { field: 123 };
+
+    let result = verify_that!(actual, matches_pattern!(AStruct { get_field(): eq(234) }));
+
+    verify_that!(
+        result,
+        err(displays_as(contains_substring(
+            "Expected: is AStruct which has property `get_field ()`"
+        )))
+    )
+}
+
+#[test]
+fn includes_struct_name_in_description_with_ref_property() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        field: u32,
+    }
+    impl AStruct {
+        fn get_field(&self) -> &u32 {
+            &self.field
+        }
+    }
+    let actual = AStruct { field: 123 };
+
+    let result = verify_that!(actual, matches_pattern!(AStruct { ref get_field(): eq(234) }));
+
+    verify_that!(
+        result,
+        err(displays_as(contains_substring(
+            "Expected: is AStruct which has property `get_field ()`"
+        )))
+    )
+}
+
+#[test]
+fn includes_struct_name_in_description_with_property_after_field() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        field: u32,
+    }
+    impl AStruct {
+        fn get_field(&self) -> u32 {
+            self.field
+        }
+    }
+    let actual = AStruct { field: 123 };
+
+    let result =
+        verify_that!(actual, matches_pattern!(AStruct { field: eq(123), get_field(): eq(234) }));
+
+    verify_that!(
+        result,
+        err(displays_as(contains_substring(
+            "Expected: is AStruct which has all the following properties"
+        )))
+    )
+}
+
+#[test]
+fn includes_struct_name_in_description_with_ref_property_after_field() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        field: u32,
+    }
+    impl AStruct {
+        fn get_field(&self) -> &u32 {
+            &self.field
+        }
+    }
+    let actual = AStruct { field: 123 };
+
+    let result = verify_that!(
+        actual,
+        matches_pattern!(AStruct { field: eq(123), ref get_field(): eq(234) })
+    );
+
+    verify_that!(
+        result,
+        err(displays_as(contains_substring(
+            "Expected: is AStruct which has all the following properties"
+        )))
+    )
+}
+
+#[test]
+fn matches_struct_with_a_method() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+    }
+
+    impl AStruct {
+        fn get_field(&self) -> u32 {
+            self.a_field
+        }
+    }
+
+    let actual = AStruct { a_field: 123 };
+
+    verify_that!(actual, matches_pattern!(AStruct { get_field(): eq(123) }))
+}
+
+#[test]
+fn matches_struct_with_a_method_and_trailing_comma() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+    }
+
+    impl AStruct {
+        fn get_field(&self) -> u32 {
+            self.a_field
+        }
+    }
+
+    let actual = AStruct { a_field: 123 };
+
+    verify_that!(actual, matches_pattern!(AStruct { get_field(): eq(123), }))
+}
+
+#[test]
+fn matches_struct_with_a_method_taking_parameter() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+    }
+
+    impl AStruct {
+        fn add_to_field(&self, a: u32) -> u32 {
+            self.a_field + a
+        }
+    }
+
+    let actual = AStruct { a_field: 1 };
+
+    verify_that!(actual, matches_pattern!(AStruct { add_to_field(2): eq(3) }))
+}
+
+#[test]
+fn matches_struct_with_a_method_taking_two_parameters() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+    }
+
+    impl AStruct {
+        fn add_product_to_field(&self, a: u32, b: u32) -> u32 {
+            self.a_field + a * b
+        }
+    }
+
+    let actual = AStruct { a_field: 1 };
+
+    verify_that!(actual, matches_pattern!(AStruct { add_product_to_field(2, 3): eq(7) }))
+}
+
+#[test]
+fn matches_struct_with_a_method_taking_enum_value_parameter() -> Result<()> {
+    enum AnEnum {
+        AVariant,
+    }
+
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+    }
+
+    impl AStruct {
+        fn get_a_field(&self, _value: AnEnum) -> u32 {
+            self.a_field
+        }
+    }
+
+    let actual = AStruct { a_field: 1 };
+
+    verify_that!(actual, matches_pattern!(AStruct { get_a_field(AnEnum::AVariant): eq(1) }))
+}
+
+#[test]
+fn matches_struct_with_a_method_taking_two_parameters_with_trailing_comma() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+    }
+
+    impl AStruct {
+        fn add_product_to_field(&self, a: u32, b: u32) -> u32 {
+            self.a_field + a * b
+        }
+    }
+
+    let actual = AStruct { a_field: 1 };
+
+    verify_that!(actual, matches_pattern!(AStruct { add_product_to_field(2, 3,): eq(7) }))
+}
+
+#[test]
+fn matches_struct_with_a_method_returning_a_reference() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+    }
+
+    impl AStruct {
+        fn get_field_ref(&self) -> &u32 {
+            &self.a_field
+        }
+    }
+
+    let actual = AStruct { a_field: 123 };
+
+    verify_that!(actual, matches_pattern!(AStruct { ref get_field_ref(): eq(123) }))
+}
+
+#[test]
+fn matches_struct_with_a_method_returning_a_reference_with_trailing_comma() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+    }
+
+    impl AStruct {
+        fn get_field_ref(&self) -> &u32 {
+            &self.a_field
+        }
+    }
+
+    let actual = AStruct { a_field: 123 };
+
+    verify_that!(actual, matches_pattern!(AStruct { ref get_field_ref(): eq(123), }))
+}
+
+#[test]
+fn matches_struct_with_a_method_taking_two_parameters_ret_ref() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+    }
+
+    impl AStruct {
+        fn get_field_ref(&self, _a: u32, _b: u32) -> &u32 {
+            &self.a_field
+        }
+    }
+
+    let actual = AStruct { a_field: 1 };
+
+    verify_that!(actual, matches_pattern!(AStruct { ref get_field_ref(2, 3): eq(1) }))
+}
+
+#[test]
+fn matches_struct_with_a_method_returning_reference_taking_enum_value_parameter() -> Result<()> {
+    enum AnEnum {
+        AVariant,
+    }
+
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+    }
+
+    impl AStruct {
+        fn get_field_ref(&self, _value: AnEnum) -> &u32 {
+            &self.a_field
+        }
+    }
+
+    let actual = AStruct { a_field: 1 };
+
+    verify_that!(actual, matches_pattern!(AStruct { ref get_field_ref(AnEnum::AVariant): eq(1) }))
+}
+
+#[test]
+fn matches_struct_with_a_method_taking_two_parameters_with_trailing_comma_ret_ref() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+    }
+
+    impl AStruct {
+        fn get_field_ref(&self, _a: u32, _b: u32) -> &u32 {
+            &self.a_field
+        }
+    }
+
+    let actual = AStruct { a_field: 1 };
+
+    verify_that!(actual, matches_pattern!(AStruct { ref get_field_ref(2, 3,): eq(1) }))
+}
+
+#[test]
+fn matches_struct_with_a_method_followed_by_a_field() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+        another_field: u32,
+    }
+
+    impl AStruct {
+        fn get_field(&self) -> u32 {
+            self.a_field
+        }
+    }
+
+    let actual = AStruct { a_field: 123, another_field: 234 };
+
+    verify_that!(actual, matches_pattern!(AStruct { get_field(): eq(123), another_field: eq(234) }))
+}
+
+#[test]
+fn matches_struct_with_a_method_followed_by_a_field_with_trailing_comma() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+        another_field: u32,
+    }
+
+    impl AStruct {
+        fn get_field(&self) -> u32 {
+            self.a_field
+        }
+    }
+
+    let actual = AStruct { a_field: 123, another_field: 234 };
+
+    verify_that!(
+        actual,
+        matches_pattern!(AStruct { get_field(): eq(123), another_field: eq(234), })
+    )
+}
+
+#[test]
+fn matches_struct_with_a_method_taking_two_parameters_and_field() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+        another_field: u32,
+    }
+
+    impl AStruct {
+        fn add_product_to_field(&self, a: u32, b: u32) -> u32 {
+            self.a_field + a * b
+        }
+    }
+
+    let actual = AStruct { a_field: 1, another_field: 123 };
+
+    verify_that!(
+        actual,
+        matches_pattern!(AStruct { add_product_to_field(2, 3): eq(7), another_field: eq(123) })
+    )
+}
+
+#[test]
+fn matches_struct_with_a_method_taking_enum_value_parameter_followed_by_field() -> Result<()> {
+    enum AnEnum {
+        AVariant,
+    }
+
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+        another_field: u32,
+    }
+
+    impl AStruct {
+        fn get_field(&self, _value: AnEnum) -> u32 {
+            self.a_field
+        }
+    }
+
+    let actual = AStruct { a_field: 1, another_field: 2 };
+
+    verify_that!(
+        actual,
+        matches_pattern!(AStruct { get_field(AnEnum::AVariant): eq(1), another_field: eq(2) })
+    )
+}
+
+#[test]
+fn matches_struct_with_a_method_taking_two_parameters_with_trailing_comma_and_field() -> Result<()>
+{
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+        another_field: u32,
+    }
+
+    impl AStruct {
+        fn add_product_to_field(&self, a: u32, b: u32) -> u32 {
+            self.a_field + a * b
+        }
+    }
+
+    let actual = AStruct { a_field: 1, another_field: 123 };
+
+    verify_that!(
+        actual,
+        matches_pattern!(AStruct { add_product_to_field(2, 3,): eq(7), another_field: eq(123) })
+    )
+}
+
+#[test]
+fn matches_struct_with_a_method_returning_reference_followed_by_a_field() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+        another_field: u32,
+    }
+
+    impl AStruct {
+        fn get_field_ref(&self) -> &u32 {
+            &self.a_field
+        }
+    }
+
+    let actual = AStruct { a_field: 123, another_field: 234 };
+
+    verify_that!(
+        actual,
+        matches_pattern!(AStruct { ref get_field_ref(): eq(123), another_field: eq(234) })
+    )
+}
+
+#[test]
+fn matches_struct_with_a_method_returning_reference_followed_by_a_field_with_trailing_comma()
+-> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+        another_field: u32,
+    }
+
+    impl AStruct {
+        fn get_field_ref(&self) -> &u32 {
+            &self.a_field
+        }
+    }
+
+    let actual = AStruct { a_field: 123, another_field: 234 };
+
+    verify_that!(
+        actual,
+        matches_pattern!(AStruct { ref get_field_ref(): eq(123), another_field: eq(234), })
+    )
+}
+
+#[test]
+fn matches_struct_with_a_method_taking_two_parameters_ret_ref_and_field() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+        another_field: u32,
+    }
+
+    impl AStruct {
+        fn get_field_ref(&self, _a: u32, _b: u32) -> &u32 {
+            &self.a_field
+        }
+    }
+
+    let actual = AStruct { a_field: 1, another_field: 123 };
+
+    verify_that!(
+        actual,
+        matches_pattern!(AStruct { ref get_field_ref(2, 3): eq(1), another_field: eq(123) })
+    )
+}
+
+#[test]
+fn matches_struct_with_a_method_taking_enum_value_param_ret_ref_followed_by_field() -> Result<()> {
+    enum AnEnum {
+        AVariant,
+    }
+
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+        another_field: u32,
+    }
+
+    impl AStruct {
+        fn get_field_ref(&self, _value: AnEnum) -> &u32 {
+            &self.a_field
+        }
+    }
+
+    let actual = AStruct { a_field: 1, another_field: 2 };
+
+    verify_that!(
+        actual,
+        matches_pattern!(AStruct { ref get_field_ref(AnEnum::AVariant): eq(1), another_field: eq(2) })
+    )
+}
+
+#[test]
+fn matches_struct_with_a_method_taking_two_parameters_with_trailing_comma_ret_ref_and_field()
+-> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+        another_field: u32,
+    }
+
+    impl AStruct {
+        fn get_field_ref(&self, _a: u32, _b: u32) -> &u32 {
+            &self.a_field
+        }
+    }
+
+    let actual = AStruct { a_field: 1, another_field: 123 };
+
+    verify_that!(
+        actual,
+        matches_pattern!(AStruct { ref get_field_ref(2, 3,): eq(1), another_field: eq(123) })
+    )
+}
+
+#[test]
+fn matches_struct_with_a_field_followed_by_a_method() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+        another_field: u32,
+    }
+
+    impl AStruct {
+        fn get_field(&self) -> u32 {
+            self.a_field
+        }
+    }
+
+    let actual = AStruct { a_field: 123, another_field: 234 };
+
+    verify_that!(actual, matches_pattern!(AStruct { another_field: eq(234), get_field(): eq(123) }))
+}
+
+#[test]
+fn matches_struct_with_a_field_followed_by_a_method_with_trailing_comma() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+        another_field: u32,
+    }
+
+    impl AStruct {
+        fn get_field(&self) -> u32 {
+            self.a_field
+        }
+    }
+
+    let actual = AStruct { a_field: 123, another_field: 234 };
+
+    verify_that!(
+        actual,
+        matches_pattern!(AStruct { another_field: eq(234), get_field(): eq(123), })
+    )
+}
+
+#[test]
+fn matches_struct_with_a_field_followed_by_a_method_with_params() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+        another_field: u32,
+    }
+
+    impl AStruct {
+        fn add_product_to_field(&self, a: u32, b: u32) -> u32 {
+            self.a_field + a * b
+        }
+    }
+
+    let actual = AStruct { a_field: 1, another_field: 234 };
+
+    verify_that!(
+        actual,
+        matches_pattern!(AStruct { another_field: eq(234), add_product_to_field(2, 3): eq(7) })
+    )
+}
+
+#[test]
+fn matches_struct_with_field_followed_by_method_taking_enum_value_param() -> Result<()> {
+    enum AnEnum {
+        AVariant,
+    }
+
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+        another_field: u32,
+    }
+
+    impl AStruct {
+        fn get_field(&self, _value: AnEnum) -> u32 {
+            self.a_field
+        }
+    }
+
+    let actual = AStruct { a_field: 1, another_field: 2 };
+
+    verify_that!(
+        actual,
+        matches_pattern!(AStruct { another_field: eq(2), get_field(AnEnum::AVariant): eq(1) })
+    )
+}
+
+#[test]
+fn matches_struct_with_a_field_followed_by_a_method_with_params_and_trailing_comma() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+        another_field: u32,
+    }
+
+    impl AStruct {
+        fn add_product_to_field(&self, a: u32, b: u32) -> u32 {
+            self.a_field + a * b
+        }
+    }
+
+    let actual = AStruct { a_field: 1, another_field: 234 };
+
+    verify_that!(
+        actual,
+        matches_pattern!(AStruct { another_field: eq(234), add_product_to_field(2, 3,): eq(7) })
+    )
+}
+
+#[test]
+fn matches_struct_with_a_field_followed_by_a_method_returning_reference() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+        another_field: u32,
+    }
+
+    impl AStruct {
+        fn get_field_ref(&self) -> &u32 {
+            &self.a_field
+        }
+    }
+
+    let actual = AStruct { a_field: 123, another_field: 234 };
+
+    verify_that!(
+        actual,
+        matches_pattern!(AStruct { another_field: eq(234), ref get_field_ref(): eq(123) })
+    )
+}
+
+#[test]
+fn matches_struct_with_a_field_followed_by_a_method_returning_ref_and_trailing_comma() -> Result<()>
+{
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+        another_field: u32,
+    }
+
+    impl AStruct {
+        fn get_field_ref(&self) -> &u32 {
+            &self.a_field
+        }
+    }
+
+    let actual = AStruct { a_field: 123, another_field: 234 };
+
+    verify_that!(
+        actual,
+        matches_pattern!(AStruct { another_field: eq(234), ref get_field_ref(): eq(123), })
+    )
+}
+
+#[test]
+fn matches_struct_with_a_field_followed_by_a_method_with_params_ret_ref() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+        another_field: u32,
+    }
+
+    impl AStruct {
+        fn get_field_ref(&self, _a: u32, _b: u32) -> &u32 {
+            &self.a_field
+        }
+    }
+
+    let actual = AStruct { a_field: 123, another_field: 234 };
+
+    verify_that!(
+        actual,
+        matches_pattern!(AStruct { another_field: eq(234), ref get_field_ref(2, 3): eq(123) })
+    )
+}
+
+#[test]
+fn matches_struct_with_field_followed_by_method_taking_enum_value_param_ret_ref() -> Result<()> {
+    enum AnEnum {
+        AVariant,
+    }
+
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+        another_field: u32,
+    }
+
+    impl AStruct {
+        fn get_field_ref(&self, _value: AnEnum) -> &u32 {
+            &self.a_field
+        }
+    }
+
+    let actual = AStruct { a_field: 1, another_field: 2 };
+
+    verify_that!(
+        actual,
+        matches_pattern!(AStruct { another_field: eq(2), ref get_field_ref(AnEnum::AVariant): eq(1) })
+    )
+}
+
+#[test]
+fn matches_struct_with_a_field_followed_by_a_method_with_params_and_trailing_comma_ret_ref()
+-> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+        another_field: u32,
+    }
+
+    impl AStruct {
+        fn get_field_ref(&self, _a: u32, _b: u32) -> &u32 {
+            &self.a_field
+        }
+    }
+
+    let actual = AStruct { a_field: 123, another_field: 234 };
+
+    verify_that!(
+        actual,
+        matches_pattern!(AStruct { another_field: eq(234), ref get_field_ref(2, 3,): eq(123) })
+    )
+}
+
+#[test]
+fn matches_struct_with_a_field_followed_by_a_method_followed_by_a_field() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+        another_field: u32,
+        a_third_field: u32,
+    }
+
+    impl AStruct {
+        fn get_field(&self) -> u32 {
+            self.a_field
+        }
+    }
+
+    let actual = AStruct { a_field: 123, another_field: 234, a_third_field: 345 };
+
+    verify_that!(
+        actual,
+        matches_pattern!(AStruct {
+            another_field: eq(234),
+            get_field(): eq(123),
+            a_third_field: eq(345)
+        })
+    )
+}
+
+#[test]
+fn matches_struct_with_a_field_followed_by_a_method_followed_by_a_field_with_trailing_comma()
+-> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+        another_field: u32,
+        a_third_field: u32,
+    }
+
+    impl AStruct {
+        fn get_field(&self) -> u32 {
+            self.a_field
+        }
+    }
+
+    let actual = AStruct { a_field: 123, another_field: 234, a_third_field: 345 };
+
+    verify_that!(
+        actual,
+        matches_pattern!(AStruct {
+            another_field: eq(234),
+            get_field(): eq(123),
+            a_third_field: eq(345),
+        })
+    )
+}
+
+#[test]
+fn matches_struct_with_a_field_followed_by_a_method_with_params_followed_by_a_field() -> Result<()>
+{
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+        another_field: u32,
+        a_third_field: u32,
+    }
+
+    impl AStruct {
+        fn add_product_to_field(&self, a: u32, b: u32) -> u32 {
+            self.a_field + a * b
+        }
+    }
+
+    let actual = AStruct { a_field: 1, another_field: 234, a_third_field: 345 };
+
+    verify_that!(
+        actual,
+        matches_pattern!(AStruct {
+            another_field: eq(234),
+            add_product_to_field(2, 3): eq(7),
+            a_third_field: eq(345),
+        })
+    )
+}
+
+#[test]
+fn matches_struct_with_a_field_followed_by_a_method_with_params_and_trailing_comma_followed_by_a_field()
+-> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+        another_field: u32,
+        a_third_field: u32,
+    }
+
+    impl AStruct {
+        fn add_product_to_field(&self, a: u32, b: u32) -> u32 {
+            self.a_field + a * b
+        }
+    }
+
+    let actual = AStruct { a_field: 1, another_field: 234, a_third_field: 345 };
+
+    verify_that!(
+        actual,
+        matches_pattern!(AStruct {
+            another_field: eq(234),
+            add_product_to_field(2, 3,): eq(7),
+            a_third_field: eq(345),
+        })
+    )
+}
+
+#[test]
+fn matches_struct_with_field_followed_by_method_taking_enum_value_param_followed_by_field()
+-> Result<()> {
+    enum AnEnum {
+        AVariant,
+    }
+
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+        another_field: u32,
+        a_third_field: u32,
+    }
+
+    impl AStruct {
+        fn get_field(&self, _value: AnEnum) -> u32 {
+            self.a_field
+        }
+    }
+
+    let actual = AStruct { a_field: 1, another_field: 2, a_third_field: 3 };
+
+    verify_that!(
+        actual,
+        matches_pattern!(AStruct {
+            another_field: eq(2),
+            get_field(AnEnum::AVariant): eq(1),
+            a_third_field: eq(3),
+        })
+    )
+}
+
+#[test]
+fn matches_struct_with_a_field_followed_by_a_method_ret_ref_followed_by_a_field() -> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+        another_field: u32,
+        a_third_field: u32,
+    }
+
+    impl AStruct {
+        fn get_field_ref(&self) -> &u32 {
+            &self.a_field
+        }
+    }
+
+    let actual = AStruct { a_field: 123, another_field: 234, a_third_field: 345 };
+
+    verify_that!(
+        actual,
+        matches_pattern!(AStruct {
+            another_field: eq(234),
+            ref get_field_ref(): eq(123),
+            a_third_field: eq(345)
+        })
+    )
+}
+
+#[test]
+fn matches_struct_with_a_field_followed_by_a_method_ret_ref_followed_by_a_field_with_trailing_comma()
+-> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+        another_field: u32,
+        a_third_field: u32,
+    }
+
+    impl AStruct {
+        fn get_field_ref(&self) -> &u32 {
+            &self.a_field
+        }
+    }
+
+    let actual = AStruct { a_field: 123, another_field: 234, a_third_field: 345 };
+
+    verify_that!(
+        actual,
+        matches_pattern!(AStruct {
+            another_field: eq(234),
+            ref get_field_ref(): eq(123),
+            a_third_field: eq(345),
+        })
+    )
+}
+
+#[test]
+fn matches_struct_with_a_field_followed_by_a_method_with_params_ret_ref_followed_by_a_field()
+-> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+        another_field: u32,
+        a_third_field: u32,
+    }
+
+    impl AStruct {
+        fn get_field_ref(&self, _a: u32, _b: u32) -> &u32 {
+            &self.a_field
+        }
+    }
+
+    let actual = AStruct { a_field: 123, another_field: 234, a_third_field: 345 };
+
+    verify_that!(
+        actual,
+        matches_pattern!(AStruct {
+            another_field: eq(234),
+            ref get_field_ref(2, 3): eq(123),
+            a_third_field: eq(345),
+        })
+    )
+}
+
+#[test]
+fn matches_struct_with_field_followed_by_method_taking_enum_value_param_ret_ref_followed_by_field()
+-> Result<()> {
+    enum AnEnum {
+        AVariant,
+    }
+
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+        another_field: u32,
+        a_third_field: u32,
+    }
+
+    impl AStruct {
+        fn get_field_ref(&self, _value: AnEnum) -> &u32 {
+            &self.a_field
+        }
+    }
+
+    let actual = AStruct { a_field: 1, another_field: 2, a_third_field: 3 };
+
+    verify_that!(
+        actual,
+        matches_pattern!(AStruct {
+            another_field: eq(2),
+            ref get_field_ref(AnEnum::AVariant): eq(1),
+            a_third_field: eq(3),
+        })
+    )
+}
+
+#[test]
+fn matches_struct_with_a_field_followed_by_a_method_with_params_trailing_comma_ret_ref_followed_by_a_field()
+-> Result<()> {
+    #[derive(Debug)]
+    struct AStruct {
+        a_field: u32,
+        another_field: u32,
+        a_third_field: u32,
+    }
+
+    impl AStruct {
+        fn get_field_ref(&self, _a: u32, _b: u32) -> &u32 {
+            &self.a_field
+        }
+    }
+
+    let actual = AStruct { a_field: 123, another_field: 234, a_third_field: 345 };
+
+    verify_that!(
+        actual,
+        matches_pattern!(AStruct {
+            another_field: eq(234),
+            ref get_field_ref(2, 3,): eq(123),
+            a_third_field: eq(345),
+        })
+    )
+}
diff --git a/tests/no_color_test.rs b/tests/no_color_test.rs
new file mode 100644
index 0000000..f307890
--- /dev/null
+++ b/tests/no_color_test.rs
@@ -0,0 +1,54 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#![cfg(feature = "supports-color")]
+
+use googletest::prelude::*;
+use indoc::indoc;
+use std::fmt::{Display, Write};
+
+// Make a long text with each element of the iterator on one line.
+// `collection` must contains at least one element.
+fn build_text<T: Display>(mut collection: impl Iterator<Item = T>) -> String {
+    let mut text = String::new();
+    write!(&mut text, "{}", collection.next().expect("Provided collection without elements"))
+        .unwrap();
+    for item in collection {
+        write!(&mut text, "\n{}", item).unwrap();
+    }
+    text
+}
+
+#[test]
+fn colors_suppressed_when_both_no_color_and_force_color_are_set() -> Result<()> {
+    std::env::set_var("NO_COLOR", "1");
+    std::env::set_var("FORCE_COLOR", "1");
+
+    let result = verify_that!(build_text(1..50), eq(build_text(1..51)));
+
+    verify_that!(
+        result,
+        err(displays_as(contains_substring(indoc! {
+            "
+
+            Difference(-actual / +expected):
+             1
+             2
+             <---- 45 common lines omitted ---->
+             48
+             49
+            +50"
+        })))
+    )
+}
diff --git a/tests/pointwise_matcher_test.rs b/tests/pointwise_matcher_test.rs
new file mode 100644
index 0000000..85d16ff
--- /dev/null
+++ b/tests/pointwise_matcher_test.rs
@@ -0,0 +1,170 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use googletest::prelude::*;
+use indoc::indoc;
+
+#[test]
+fn pointwise_matches_single_element() -> Result<()> {
+    let value = vec![1];
+    verify_that!(value, pointwise!(lt, vec![2]))
+}
+
+#[test]
+fn pointwise_matches_two_elements() -> Result<()> {
+    let value = vec![1, 2];
+    verify_that!(value, pointwise!(lt, vec![2, 3]))
+}
+
+#[test]
+fn pointwise_matches_two_elements_with_array() -> Result<()> {
+    let value = vec![1, 2];
+    verify_that!(value, pointwise!(lt, [2, 3]))
+}
+
+#[test]
+fn pointwise_matches_two_element_slice() -> Result<()> {
+    let value = vec![1, 2];
+    let slice = value.as_slice();
+    verify_that!(*slice, pointwise!(lt, [2, 3]))
+}
+
+#[test]
+fn pointwise_does_not_match_value_of_wrong_length() -> Result<()> {
+    let value = vec![1];
+    verify_that!(value, not(pointwise!(lt, vec![2, 3])))
+}
+
+#[test]
+fn pointwise_does_not_match_value_not_matching_in_first_position() -> Result<()> {
+    let value = vec![1, 2];
+    verify_that!(value, not(pointwise!(lt, vec![1, 3])))
+}
+
+#[test]
+fn pointwise_does_not_match_value_not_matching_in_second_position() -> Result<()> {
+    let value = vec![1, 2];
+    verify_that!(value, not(pointwise!(lt, vec![2, 2])))
+}
+
+#[test]
+fn pointwise_allows_qualified_matcher_name() -> Result<()> {
+    mod submodule {
+        pub(super) use super::lt;
+    }
+    let value = vec![1];
+    verify_that!(value, pointwise!(submodule::lt, vec![2]))
+}
+
+#[test]
+fn pointwise_returns_mismatch_when_actual_value_has_wrong_length() -> Result<()> {
+    let result = verify_that!(vec![1, 2, 3], pointwise!(eq, vec![1, 2]));
+
+    verify_that!(
+        result,
+        err(displays_as(contains_substring(indoc!(
+            "
+            Value of: vec![1, 2, 3]
+            Expected: has elements satisfying respectively:
+              0. is equal to 1
+              1. is equal to 2
+            Actual: [1, 2, 3],
+              which has size 3 (expected 2)
+            "
+        ))))
+    )
+}
+
+#[test]
+fn pointwise_returns_mismatch_when_actual_value_does_not_match_on_first_item() -> Result<()> {
+    let result = verify_that!(vec![1, 2, 3], pointwise!(eq, vec![2, 2, 3]));
+
+    verify_that!(
+        result,
+        err(displays_as(contains_substring(indoc!(
+            "
+            Value of: vec![1, 2, 3]
+            Expected: has elements satisfying respectively:
+              0. is equal to 2
+              1. is equal to 2
+              2. is equal to 3
+            Actual: [1, 2, 3],
+              where element #0 is 1, which isn't equal to 2
+            "
+        ))))
+    )
+}
+
+#[test]
+fn pointwise_returns_mismatch_when_actual_value_does_not_match_on_second_item() -> Result<()> {
+    let result = verify_that!(vec![1, 2, 3], pointwise!(eq, vec![1, 3, 3]));
+
+    verify_that!(
+        result,
+        err(displays_as(contains_substring(indoc!(
+            "
+            Value of: vec![1, 2, 3]
+            Expected: has elements satisfying respectively:
+              0. is equal to 1
+              1. is equal to 3
+              2. is equal to 3
+            Actual: [1, 2, 3],
+              where element #1 is 2, which isn't equal to 3
+            "
+        ))))
+    )
+}
+
+#[test]
+fn pointwise_returns_mismatch_when_actual_value_does_not_match_on_first_and_second_items()
+-> Result<()> {
+    let result = verify_that!(vec![1, 2, 3], pointwise!(eq, vec![2, 3, 3]));
+
+    verify_that!(
+        result,
+        err(displays_as(contains_substring(indoc!(
+            "
+            Value of: vec![1, 2, 3]
+            Expected: has elements satisfying respectively:
+              0. is equal to 2
+              1. is equal to 3
+              2. is equal to 3
+            Actual: [1, 2, 3],
+              where:
+              * element #0 is 1, which isn't equal to 2
+              * element #1 is 2, which isn't equal to 3"
+        ))))
+    )
+}
+
+#[test]
+fn pointwise_matches_single_element_with_lambda_expression_with_extra_value() -> Result<()> {
+    let value = vec![1.00001f32];
+    verify_that!(value, pointwise!(|v| near(v, 0.0001), vec![1.0]))
+}
+
+#[test]
+fn pointwise_matches_single_element_with_two_containers() -> Result<()> {
+    let value = vec![1.00001f32];
+    verify_that!(value, pointwise!(near, vec![1.0], vec![0.0001]))
+}
+
+#[test]
+fn pointwise_matches_single_element_with_three_containers() -> Result<()> {
+    let value = vec![1.00001f32];
+    verify_that!(
+        value,
+        pointwise!(|v, t, u| near(v, t * u), vec![1.0f32], vec![0.0001f32], vec![0.5f32])
+    )
+}
diff --git a/tests/property_matcher_test.rs b/tests/property_matcher_test.rs
new file mode 100644
index 0000000..f9a88be
--- /dev/null
+++ b/tests/property_matcher_test.rs
@@ -0,0 +1,189 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use googletest::matcher::{Matcher, MatcherResult};
+use googletest::prelude::*;
+
+#[derive(Debug)]
+struct SomeStruct {
+    a_property: u32,
+}
+
+impl SomeStruct {
+    fn get_property(&self) -> u32 {
+        self.a_property
+    }
+
+    fn get_property_ref(&self) -> &u32 {
+        &self.a_property
+    }
+
+    fn add_product_to_field(&self, a: u32, b: u32) -> u32 {
+        self.a_property + a * b
+    }
+
+    fn get_property_ref_with_params(&self, _a: u32, _b: u32) -> &u32 {
+        &self.a_property
+    }
+}
+
+#[test]
+fn matches_struct_with_matching_property() -> Result<()> {
+    let value = SomeStruct { a_property: 10 };
+    verify_that!(value, property!(SomeStruct.get_property(), eq(10)))
+}
+
+#[test]
+fn matches_struct_with_matching_property_with_parameters() -> Result<()> {
+    let value = SomeStruct { a_property: 10 };
+    verify_that!(value, property!(SomeStruct.add_product_to_field(2, 3), eq(16)))
+}
+
+#[test]
+fn matches_struct_with_matching_property_with_captured_arguments() -> Result<()> {
+    let value = SomeStruct { a_property: 10 };
+    let arg1 = 2;
+    let arg2 = 3;
+    verify_that!(value, property!(SomeStruct.add_product_to_field(arg1, arg2), eq(16)))
+}
+
+#[test]
+fn matches_struct_with_matching_property_with_parameters_with_trailing_comma() -> Result<()> {
+    let value = SomeStruct { a_property: 10 };
+    verify_that!(value, property!(SomeStruct.add_product_to_field(2, 3,), eq(16)))
+}
+
+#[test]
+fn matches_struct_with_matching_property_ref() -> Result<()> {
+    let value = SomeStruct { a_property: 10 };
+    verify_that!(value, property!(ref SomeStruct.get_property_ref(), eq(10)))
+}
+
+#[test]
+fn matches_struct_with_matching_string_reference_property() -> Result<()> {
+    #[derive(Debug)]
+    struct StructWithString {
+        property: String,
+    }
+    impl StructWithString {
+        fn get_property_ref(&self) -> &String {
+            &self.property
+        }
+    }
+    let value = StructWithString { property: "Something".into() };
+    verify_that!(value, property!(ref StructWithString.get_property_ref(), eq("Something")))
+}
+
+#[test]
+fn matches_struct_with_matching_slice_property() -> Result<()> {
+    #[derive(Debug)]
+    struct StructWithVec {
+        property: Vec<u32>,
+    }
+    impl StructWithVec {
+        fn get_property_ref(&self) -> &[u32] {
+            &self.property
+        }
+    }
+    let value = StructWithVec { property: vec![1, 2, 3] };
+    verify_that!(value, property!(ref StructWithVec.get_property_ref(), eq([1, 2, 3])))
+}
+
+#[test]
+fn matches_struct_with_matching_property_ref_with_parameters() -> Result<()> {
+    let value = SomeStruct { a_property: 10 };
+    verify_that!(value, property!(ref SomeStruct.get_property_ref_with_params(2, 3), eq(10)))
+}
+
+#[test]
+fn matches_struct_with_matching_property_ref_with_parameters_and_trailing_comma() -> Result<()> {
+    let value = SomeStruct { a_property: 10 };
+    verify_that!(value, property!(ref SomeStruct.get_property_ref_with_params(2, 3,), eq(10)))
+}
+
+#[test]
+fn does_not_match_struct_with_non_matching_property() -> Result<()> {
+    let value = SomeStruct { a_property: 2 };
+    verify_that!(value, not(property!(SomeStruct.get_property(), eq(1))))
+}
+
+#[test]
+fn describes_itself_in_matching_case() -> Result<()> {
+    verify_that!(
+        property!(SomeStruct.get_property(), eq(1)).describe(MatcherResult::Match),
+        eq("has property `get_property()`, which is equal to 1")
+    )
+}
+
+#[test]
+fn describes_itself_in_not_matching_case() -> Result<()> {
+    verify_that!(
+        property!(SomeStruct.get_property(), eq(1)).describe(MatcherResult::NoMatch),
+        eq("has property `get_property()`, which isn't equal to 1")
+    )
+}
+
+#[test]
+fn explains_mismatch_referencing_explanation_of_inner_matcher() -> Result<()> {
+    impl SomeStruct {
+        fn get_a_collection(&self) -> Vec<u32> {
+            vec![]
+        }
+    }
+    let value = SomeStruct { a_property: 2 };
+    let result = verify_that!(value, property!(SomeStruct.get_a_collection(), container_eq([1])));
+
+    verify_that!(
+        result,
+        err(displays_as(contains_substring(
+            "whose property `get_a_collection()` is `[]`, which is missing the element 1"
+        )))
+    )
+}
+
+#[test]
+fn describes_itself_in_matching_case_for_ref() -> Result<()> {
+    verify_that!(
+        property!(ref SomeStruct.get_property_ref(), eq(1)).describe(MatcherResult::Match),
+        eq("has property `get_property_ref()`, which is equal to 1")
+    )
+}
+
+#[test]
+fn describes_itself_in_not_matching_case_for_ref() -> Result<()> {
+    verify_that!(
+        property!(ref SomeStruct.get_property_ref(), eq(1)).describe(MatcherResult::NoMatch),
+        eq("has property `get_property_ref()`, which isn't equal to 1")
+    )
+}
+
+#[test]
+fn explains_mismatch_referencing_explanation_of_inner_matcher_for_ref() -> Result<()> {
+    static EMPTY_COLLECTION: Vec<u32> = vec![];
+    impl SomeStruct {
+        fn get_a_collection_ref(&self) -> &[u32] {
+            &EMPTY_COLLECTION
+        }
+    }
+    let value = SomeStruct { a_property: 2 };
+    let result =
+        verify_that!(value, property!(ref SomeStruct.get_a_collection_ref(), container_eq([1])));
+
+    verify_that!(
+        result,
+        err(displays_as(contains_substring(
+            "whose property `get_a_collection_ref()` is `[]`, which is missing the element 1"
+        )))
+    )
+}
diff --git a/tests/proptest_integration_test.rs b/tests/proptest_integration_test.rs
new file mode 100644
index 0000000..b33c9dd
--- /dev/null
+++ b/tests/proptest_integration_test.rs
@@ -0,0 +1,30 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#![cfg(feature = "proptest")]
+
+use googletest::prelude::*;
+use proptest::test_runner::{Config, TestRunner};
+
+#[test]
+fn numbers_are_greater_than_zero() -> Result<()> {
+    let mut runner = TestRunner::new(Config::default());
+    runner.run(&(1..100i32), |v| Ok(verify_that!(v, gt(0))?)).into_test_result()
+}
+
+#[test]
+fn strings_are_nonempty() -> Result<()> {
+    let mut runner = TestRunner::new(Config::default());
+    runner.run(&"[a-zA-Z0-9]+", |v| Ok(verify_that!(v, not(eq("")))?)).into_test_result()
+}
diff --git a/tests/tuple_matcher_test.rs b/tests/tuple_matcher_test.rs
new file mode 100644
index 0000000..0c1eb01
--- /dev/null
+++ b/tests/tuple_matcher_test.rs
@@ -0,0 +1,259 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use googletest::matcher::{Matcher, MatcherResult};
+use googletest::prelude::*;
+use indoc::indoc;
+
+#[test]
+fn empty_matcher_matches_empty_tuple() -> Result<()> {
+    verify_that!((), ())
+}
+
+#[test]
+fn singleton_matcher_matches_matching_singleton_tuple() -> Result<()> {
+    verify_that!((123,), (eq(123),))
+}
+
+#[test]
+fn singleton_matcher_does_not_match_non_matching_singleton_tuple() -> Result<()> {
+    verify_that!((123,), not((eq(456),)))
+}
+
+#[test]
+fn pair_matcher_matches_matching_pair_tuple() -> Result<()> {
+    verify_that!((123, 456), (eq(123), eq(456)))
+}
+
+#[test]
+fn pair_matcher_matches_matching_pair_tuple_with_different_types() -> Result<()> {
+    verify_that!((123, "A string"), (eq(123), eq("A string")))
+}
+
+#[test]
+fn pair_matcher_with_trailing_comma_matches_matching_pair_tuple() -> Result<()> {
+    verify_that!((123, 456), (eq(123), eq(456),))
+}
+
+#[test]
+fn tuple_matcher_matches_matching_3_tuple() -> Result<()> {
+    verify_that!((1, 2, 3), (eq(1), eq(2), eq(3)))
+}
+
+#[test]
+fn tuple_matcher_matches_matching_4_tuple() -> Result<()> {
+    verify_that!((1, 2, 3, 4), (eq(1), eq(2), eq(3), eq(4)))
+}
+
+#[test]
+fn tuple_matcher_matches_matching_5_tuple() -> Result<()> {
+    verify_that!((1, 2, 3, 4, 5), (eq(1), eq(2), eq(3), eq(4), eq(5)))
+}
+
+#[test]
+fn tuple_matcher_matches_matching_6_tuple() -> Result<()> {
+    verify_that!((1, 2, 3, 4, 5, 6), (eq(1), eq(2), eq(3), eq(4), eq(5), eq(6)))
+}
+
+#[test]
+fn tuple_matcher_matches_matching_7_tuple() -> Result<()> {
+    verify_that!((1, 2, 3, 4, 5, 6, 7), (eq(1), eq(2), eq(3), eq(4), eq(5), eq(6), eq(7)))
+}
+
+#[test]
+fn tuple_matcher_matches_matching_8_tuple() -> Result<()> {
+    verify_that!((1, 2, 3, 4, 5, 6, 7, 8), (eq(1), eq(2), eq(3), eq(4), eq(5), eq(6), eq(7), eq(8)))
+}
+
+#[test]
+fn tuple_matcher_matches_matching_9_tuple() -> Result<()> {
+    verify_that!(
+        (1, 2, 3, 4, 5, 6, 7, 8, 9),
+        (eq(1), eq(2), eq(3), eq(4), eq(5), eq(6), eq(7), eq(8), eq(9))
+    )
+}
+
+#[test]
+fn tuple_matcher_matches_matching_10_tuple() -> Result<()> {
+    verify_that!(
+        (1, 2, 3, 4, 5, 6, 7, 8, 9, 10),
+        (eq(1), eq(2), eq(3), eq(4), eq(5), eq(6), eq(7), eq(8), eq(9), eq(10))
+    )
+}
+
+#[test]
+fn tuple_matcher_matches_matching_11_tuple() -> Result<()> {
+    verify_that!(
+        (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11),
+        (eq(1), eq(2), eq(3), eq(4), eq(5), eq(6), eq(7), eq(8), eq(9), eq(10), eq(11))
+    )
+}
+
+#[test]
+fn tuple_matcher_matches_matching_12_tuple() -> Result<()> {
+    verify_that!(
+        (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12),
+        (eq(1), eq(2), eq(3), eq(4), eq(5), eq(6), eq(7), eq(8), eq(9), eq(10), eq(11), eq(12))
+    )
+}
+
+#[test]
+fn tuple_matcher_with_trailing_comma_matches_matching_3_tuple() -> Result<()> {
+    verify_that!((1, 2, 3), (eq(1), eq(2), eq(3),))
+}
+
+#[test]
+fn tuple_matcher_with_trailing_comma_matches_matching_4_tuple() -> Result<()> {
+    verify_that!((1, 2, 3, 4), (eq(1), eq(2), eq(3), eq(4),))
+}
+
+#[test]
+fn tuple_matcher_with_trailing_comma_matches_matching_5_tuple() -> Result<()> {
+    verify_that!((1, 2, 3, 4, 5), (eq(1), eq(2), eq(3), eq(4), eq(5),))
+}
+
+#[test]
+fn tuple_matcher_with_trailing_comma_matches_matching_6_tuple() -> Result<()> {
+    verify_that!((1, 2, 3, 4, 5, 6), (eq(1), eq(2), eq(3), eq(4), eq(5), eq(6),))
+}
+
+#[test]
+fn tuple_matcher_with_trailing_comma_matches_matching_7_tuple() -> Result<()> {
+    verify_that!((1, 2, 3, 4, 5, 6, 7), (eq(1), eq(2), eq(3), eq(4), eq(5), eq(6), eq(7),))
+}
+
+#[test]
+fn tuple_matcher_with_trailing_comma_matches_matching_8_tuple() -> Result<()> {
+    verify_that!(
+        (1, 2, 3, 4, 5, 6, 7, 8),
+        (eq(1), eq(2), eq(3), eq(4), eq(5), eq(6), eq(7), eq(8),)
+    )
+}
+
+#[test]
+fn tuple_matcher_with_trailing_comma_matches_matching_9_tuple() -> Result<()> {
+    verify_that!(
+        (1, 2, 3, 4, 5, 6, 7, 8, 9),
+        (eq(1), eq(2), eq(3), eq(4), eq(5), eq(6), eq(7), eq(8), eq(9),)
+    )
+}
+
+#[test]
+fn tuple_matcher_with_trailing_comma_matches_matching_10_tuple() -> Result<()> {
+    verify_that!(
+        (1, 2, 3, 4, 5, 6, 7, 8, 9, 10),
+        (eq(1), eq(2), eq(3), eq(4), eq(5), eq(6), eq(7), eq(8), eq(9), eq(10),)
+    )
+}
+
+#[test]
+fn tuple_matcher_with_trailing_comma_matches_matching_11_tuple() -> Result<()> {
+    verify_that!(
+        (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11),
+        (eq(1), eq(2), eq(3), eq(4), eq(5), eq(6), eq(7), eq(8), eq(9), eq(10), eq(11),)
+    )
+}
+
+#[test]
+fn tuple_matcher_with_trailing_comma_matches_matching_12_tuple() -> Result<()> {
+    verify_that!(
+        (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12),
+        (eq(1), eq(2), eq(3), eq(4), eq(5), eq(6), eq(7), eq(8), eq(9), eq(10), eq(11), eq(12),)
+    )
+}
+
+#[test]
+fn tuple_matcher_1_has_correct_description_for_match() -> Result<()> {
+    verify_that!(
+        (eq(1),).describe(MatcherResult::Match),
+        eq(indoc!(
+            "
+            is a tuple whose values respectively match:
+              is equal to 1,
+            "
+        ))
+    )
+}
+
+#[test]
+fn tuple_matcher_1_has_correct_description_for_mismatch() -> Result<()> {
+    verify_that!(
+        (eq(1),).describe(MatcherResult::NoMatch),
+        eq(indoc!(
+            "
+            is a tuple whose values do not respectively match:
+              is equal to 1,
+            "
+        ))
+    )
+}
+
+#[test]
+fn tuple_matcher_2_has_correct_description_for_match() -> Result<()> {
+    verify_that!(
+        (eq(1), eq(2)).describe(MatcherResult::Match),
+        eq(indoc!(
+            "
+            is a tuple whose values respectively match:
+              is equal to 1,
+              is equal to 2,
+            "
+        ))
+    )
+}
+
+#[test]
+fn tuple_matcher_2_has_correct_description_for_mismatch() -> Result<()> {
+    verify_that!(
+        (eq(1), eq(2)).describe(MatcherResult::NoMatch),
+        eq(indoc!(
+            "
+            is a tuple whose values do not respectively match:
+              is equal to 1,
+              is equal to 2,
+            "
+        ))
+    )
+}
+
+#[test]
+fn describe_match_shows_which_tuple_element_did_not_match() -> Result<()> {
+    verify_that!(
+        (eq(1), eq(2)).explain_match(&(1, 3)),
+        displays_as(eq(indoc!(
+            "
+            which is a tuple whose values do not respectively match:
+              is equal to 1,
+              is equal to 2,
+            Element #1 is 3, which isn't equal to 2
+            "
+        )))
+    )
+}
+
+#[test]
+fn describe_match_shows_which_two_tuple_elements_did_not_match() -> Result<()> {
+    verify_that!(
+        (eq(1), eq(2)).explain_match(&(2, 3)),
+        displays_as(eq(indoc!(
+            "
+            which is a tuple whose values do not respectively match:
+              is equal to 1,
+              is equal to 2,
+            Element #0 is 2, which isn't equal to 1
+            Element #1 is 3, which isn't equal to 2
+            "
+        )))
+    )
+}
diff --git a/tests/unordered_elements_are_matcher_test.rs b/tests/unordered_elements_are_matcher_test.rs
new file mode 100644
index 0000000..a105b70
--- /dev/null
+++ b/tests/unordered_elements_are_matcher_test.rs
@@ -0,0 +1,403 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use googletest::matcher::Matcher;
+use googletest::prelude::*;
+use indoc::indoc;
+use std::collections::HashMap;
+
+#[test]
+fn unordered_elements_are_matches_empty_vector() -> Result<()> {
+    let value: Vec<u32> = vec![];
+    verify_that!(value, unordered_elements_are![])
+}
+
+#[test]
+fn unordered_elements_are_matches_empty_vector_with_trailing_comma() -> Result<()> {
+    let value: Vec<u32> = vec![];
+    verify_that!(value, unordered_elements_are![,])
+}
+
+#[test]
+fn unordered_elements_are_matches_vector() -> Result<()> {
+    let value = vec![1, 2, 3];
+    verify_that!(value, unordered_elements_are![eq(1), eq(2), eq(3)])
+}
+
+#[test]
+fn unordered_elements_are_omitted() -> Result<()> {
+    let value = vec![1, 2, 3];
+    verify_that!(value, {eq(3), eq(2), eq(1)})
+}
+
+#[test]
+fn unordered_elements_are_matches_slice() -> Result<()> {
+    let value = vec![1, 2, 3];
+    let slice = value.as_slice();
+    verify_that!(*slice, unordered_elements_are![eq(1), eq(2), eq(3)])
+}
+
+#[test]
+fn unordered_elements_are_matches_hash_map() -> Result<()> {
+    let value: HashMap<u32, &'static str> = HashMap::from([(1, "One"), (2, "Two"), (3, "Three")]);
+    verify_that!(
+        value,
+        unordered_elements_are![(eq(2), eq("Two")), (eq(1), eq("One")), (eq(3), eq("Three"))]
+    )
+}
+
+#[test]
+fn unordered_elements_are_matches_hash_map_with_trailing_comma() -> Result<()> {
+    let value: HashMap<u32, &'static str> = HashMap::from([(1, "One"), (2, "Two"), (3, "Three")]);
+    verify_that!(
+        value,
+        unordered_elements_are![(eq(2), eq("Two")), (eq(1), eq("One")), (eq(3), eq("Three")),]
+    )
+}
+
+#[test]
+fn unordered_elements_are_does_not_match_hash_map_with_wrong_key() -> Result<()> {
+    let value: HashMap<u32, &'static str> = HashMap::from([(1, "One"), (2, "Two"), (4, "Three")]);
+    verify_that!(
+        value,
+        not(unordered_elements_are![(eq(2), eq("Two")), (eq(1), eq("One")), (eq(3), eq("Three"))])
+    )
+}
+
+#[test]
+fn unordered_elements_are_does_not_match_hash_map_with_wrong_value() -> Result<()> {
+    let value: HashMap<u32, &'static str> = HashMap::from([(1, "One"), (2, "Two"), (3, "Four")]);
+    verify_that!(
+        value,
+        not(unordered_elements_are![(eq(2), eq("Two")), (eq(1), eq("One")), (eq(3), eq("Three"))])
+    )
+}
+
+#[test]
+fn unordered_elements_are_does_not_match_hash_map_missing_element() -> Result<()> {
+    let value: HashMap<u32, &'static str> = HashMap::from([(1, "One"), (2, "Two")]);
+    verify_that!(
+        value,
+        not(unordered_elements_are![(eq(2), eq("Two")), (eq(1), eq("One")), (eq(3), eq("Three"))])
+    )
+}
+
+#[test]
+fn unordered_elements_are_does_not_match_hash_map_with_extra_element() -> Result<()> {
+    let value: HashMap<u32, &'static str> = HashMap::from([(1, "One"), (2, "Two"), (3, "Three")]);
+    verify_that!(value, not(unordered_elements_are![(eq(2), eq("Two")), (eq(1), eq("One"))]))
+}
+
+#[test]
+fn unordered_elements_are_does_not_match_hash_map_with_mismatched_key_and_value() -> Result<()> {
+    let value: HashMap<u32, &'static str> = HashMap::from([(1, "One"), (2, "Three"), (3, "Two")]);
+    verify_that!(
+        value,
+        not(unordered_elements_are![(eq(2), eq("Two")), (eq(1), eq("One")), (eq(3), eq("Three"))])
+    )
+}
+
+#[test]
+fn unordered_elements_are_matches_vector_with_trailing_comma() -> Result<()> {
+    let value = vec![1, 2, 3];
+    verify_that!(value, unordered_elements_are![eq(1), eq(2), eq(3),])
+}
+
+#[test]
+fn unordered_elements_are_matches_size() -> Result<()> {
+    let value = vec![1, 2];
+    verify_that!(value, not(unordered_elements_are![eq(1), eq(2), eq(3)]))
+}
+
+#[test]
+fn unordered_elements_are_admits_matchers_without_static_lifetime() -> Result<()> {
+    #[derive(Debug, PartialEq)]
+    struct AStruct(i32);
+    let expected_value = AStruct(123);
+    verify_that!(vec![AStruct(123)], unordered_elements_are![eq_deref_of(&expected_value)])
+}
+
+#[test]
+fn unordered_elements_are_with_map_admits_matchers_without_static_lifetime() -> Result<()> {
+    #[derive(Debug, PartialEq)]
+    struct AStruct(i32);
+    let expected_value = AStruct(123);
+    verify_that!(
+        HashMap::from([(1, AStruct(123))]),
+        unordered_elements_are![(eq(1), eq_deref_of(&expected_value))]
+    )
+}
+
+#[test]
+fn unordered_elements_are_description_mismatch() -> Result<()> {
+    let result = verify_that!(vec![1, 4, 3], unordered_elements_are![eq(1), eq(2), eq(3)]);
+    verify_that!(
+        result,
+        err(displays_as(contains_substring(indoc!(
+            "
+            Value of: vec![1, 4, 3]
+            Expected: contains elements matching in any order:
+              0. is equal to 1
+              1. is equal to 2
+              2. is equal to 3
+            Actual: [1, 4, 3],
+              whose element #1 does not match any expected elements and no elements match the expected element #1"
+            ))))
+    )
+}
+
+#[test]
+fn unordered_elements_are_matches_unordered() -> Result<()> {
+    let value = vec![1, 2];
+    verify_that!(value, unordered_elements_are![eq(2), eq(1)])
+}
+
+#[test]
+fn unordered_elements_are_matches_unordered_with_repetition() -> Result<()> {
+    let value = vec![1, 2, 1, 2, 1];
+    verify_that!(value, unordered_elements_are![eq(1), eq(1), eq(1), eq(2), eq(2)])
+}
+
+#[test]
+fn unordered_elements_are_explains_mismatch_due_to_wrong_size() -> Result<()> {
+    verify_that!(
+        unordered_elements_are![eq(2), eq(3), eq(4)].explain_match(&vec![2, 3]),
+        displays_as(eq("which has size 2 (expected 3)"))
+    )
+}
+
+#[test]
+fn unordered_elements_are_description_no_full_match() -> Result<()> {
+    verify_that!(
+        unordered_elements_are![eq(1), eq(2), eq(2)].explain_match(&vec![1, 1, 2]),
+        displays_as(eq(indoc!(
+            "
+            which does not have a perfect match with the expected elements. The best match found was:
+              Actual element 1 at index 0 matched expected element `is equal to 1` at index 0.
+              Actual element 2 at index 2 matched expected element `is equal to 2` at index 1.
+              Actual element 1 at index 1 did not match any remaining expected element.
+              Expected element `is equal to 2` at index 2 did not match any remaining actual element."
+        )))
+    )
+}
+
+#[test]
+fn unordered_elements_are_unmatchable_expected_description_mismatch() -> Result<()> {
+    verify_that!(
+        unordered_elements_are![eq(1), eq(2), eq(3)].explain_match(&vec![1, 1, 3]),
+        displays_as(eq("which has no element matching the expected element #1"))
+    )
+}
+
+#[test]
+fn unordered_elements_are_unmatchable_actual_description_mismatch() -> Result<()> {
+    verify_that!(
+        unordered_elements_are![eq(1), eq(1), eq(3)].explain_match(&vec![1, 2, 3]),
+        displays_as(eq("whose element #1 does not match any expected elements"))
+    )
+}
+
+fn create_matcher() -> impl Matcher<ActualT = Vec<i32>> {
+    unordered_elements_are![eq(1)]
+}
+
+#[test]
+fn unordered_elements_are_works_when_matcher_is_created_in_subroutine() -> Result<()> {
+    verify_that!(vec![1], create_matcher())
+}
+
+fn create_matcher_for_map() -> impl Matcher<ActualT = HashMap<i32, i32>> {
+    unordered_elements_are![(eq(1), eq(1))]
+}
+
+#[test]
+fn unordered_elements_are_works_when_matcher_for_maps_is_created_in_subroutine() -> Result<()> {
+    verify_that!(HashMap::from([(1, 1)]), create_matcher_for_map())
+}
+
+#[test]
+fn contains_each_matches_when_one_to_one_correspondence_present() -> Result<()> {
+    verify_that!(vec![2, 3, 4], contains_each!(eq(2), eq(3), eq(4)))
+}
+
+#[test]
+fn contains_each_supports_trailing_comma() -> Result<()> {
+    verify_that!(vec![2, 3, 4], contains_each!(eq(2), eq(3), eq(4),))
+}
+
+#[test]
+fn contains_each_matches_hash_map() -> Result<()> {
+    let value: HashMap<u32, &'static str> = HashMap::from([(1, "One"), (2, "Two"), (3, "Three")]);
+    verify_that!(value, contains_each![(eq(2), eq("Two")), (eq(1), eq("One"))])
+}
+
+#[test]
+fn contains_each_matches_hash_map_with_trailing_comma() -> Result<()> {
+    let value: HashMap<u32, &'static str> = HashMap::from([(1, "One"), (2, "Two"), (3, "Three")]);
+    verify_that!(value, contains_each![(eq(2), eq("Two")), (eq(1), eq("One")),])
+}
+
+#[test]
+fn contains_each_matches_when_no_matchers_present() -> Result<()> {
+    verify_that!(vec![2, 3, 4], contains_each!())
+}
+
+#[test]
+fn contains_each_matches_when_no_matchers_present_and_trailing_comma() -> Result<()> {
+    verify_that!(vec![2, 3, 4], contains_each!(,))
+}
+
+#[test]
+fn contains_each_matches_when_list_is_empty_and_no_matchers_present() -> Result<()> {
+    verify_that!(Vec::<u32>::new(), contains_each!())
+}
+
+#[test]
+fn contains_each_matches_when_excess_elements_present() -> Result<()> {
+    verify_that!(vec![1, 2, 3, 4], contains_each!(eq(2), eq(3), eq(4)))
+}
+
+#[test]
+fn contains_each_does_not_match_when_matchers_are_unmatched() -> Result<()> {
+    verify_that!(vec![1, 2, 3], not(contains_each!(eq(2), eq(3), eq(4))))
+}
+
+#[test]
+fn contains_each_explains_mismatch_due_to_wrong_size() -> Result<()> {
+    verify_that!(
+        contains_each![eq(2), eq(3), eq(4)].explain_match(&vec![2, 3]),
+        displays_as(eq("which has size 2 (expected at least 3)"))
+    )
+}
+
+#[test]
+fn contains_each_explains_missing_element_in_mismatch() -> Result<()> {
+    verify_that!(
+        contains_each![eq(2), eq(3), eq(4)].explain_match(&vec![1, 2, 3]),
+        displays_as(eq("which has no element matching the expected element #2"))
+    )
+}
+
+#[test]
+fn contains_each_explains_missing_elements_in_mismatch() -> Result<()> {
+    verify_that!(
+        contains_each![eq(2), eq(3), eq(4), eq(5)].explain_match(&vec![0, 1, 2, 3]),
+        displays_as(eq("which has no elements matching the expected elements #2, #3"))
+    )
+}
+
+#[test]
+fn contains_each_explains_mismatch_due_to_no_graph_matching_found() -> Result<()> {
+    verify_that!(
+        contains_each![ge(2), ge(2)].explain_match(&vec![1, 2]),
+        displays_as(eq(indoc!(
+            "
+            which does not have a superset match with the expected elements. The best match found was:
+              Actual element 2 at index 1 matched expected element `is greater than or equal to 2` at index 0.
+              Actual element 1 at index 0 did not match any remaining expected element.
+              Expected element `is greater than or equal to 2` at index 1 did not match any remaining actual element."))
+    ))
+}
+
+#[test]
+fn is_contained_in_matches_with_empty_vector() -> Result<()> {
+    let value: Vec<u32> = vec![];
+    verify_that!(value, is_contained_in!())
+}
+
+#[test]
+fn is_contained_in_matches_with_empty_vector_and_trailing_comma() -> Result<()> {
+    let value: Vec<u32> = vec![];
+    verify_that!(value, is_contained_in!(,))
+}
+
+#[test]
+fn is_contained_in_matches_when_one_to_one_correspondence_present() -> Result<()> {
+    verify_that!(vec![2, 3, 4], is_contained_in!(eq(2), eq(3), eq(4)))
+}
+
+#[test]
+fn is_contained_supports_trailing_comma() -> Result<()> {
+    verify_that!(vec![2, 3, 4], is_contained_in!(eq(2), eq(3), eq(4),))
+}
+
+#[test]
+fn is_contained_in_matches_hash_map() -> Result<()> {
+    let value: HashMap<u32, &'static str> = HashMap::from([(1, "One"), (2, "Two")]);
+    verify_that!(
+        value,
+        is_contained_in![(eq(2), eq("Two")), (eq(1), eq("One")), (eq(3), eq("Three"))]
+    )
+}
+
+#[test]
+fn is_contained_in_matches_hash_map_with_trailing_comma() -> Result<()> {
+    let value: HashMap<u32, &'static str> = HashMap::from([(1, "One"), (2, "Two")]);
+    verify_that!(
+        value,
+        is_contained_in![(eq(2), eq("Two")), (eq(1), eq("One")), (eq(3), eq("Three")),]
+    )
+}
+
+#[test]
+fn is_contained_in_matches_when_container_is_empty() -> Result<()> {
+    verify_that!(vec![], is_contained_in!(eq(2), eq(3), eq(4)))
+}
+
+#[test]
+fn is_contained_in_matches_when_excess_matchers_present() -> Result<()> {
+    verify_that!(vec![3, 4], is_contained_in!(eq(2), eq(3), eq(4)))
+}
+
+#[test]
+fn is_contained_in_does_not_match_when_elements_are_unmatched() -> Result<()> {
+    verify_that!(vec![1, 2, 3], not(is_contained_in!(eq(2), eq(3), eq(4))))
+}
+
+#[test]
+fn is_contained_in_explains_mismatch_due_to_wrong_size() -> Result<()> {
+    verify_that!(
+        is_contained_in![eq(2), eq(3)].explain_match(&vec![2, 3, 4]),
+        displays_as(eq("which has size 3 (expected at most 2)"))
+    )
+}
+
+#[test]
+fn is_contained_in_explains_missing_element_in_mismatch() -> Result<()> {
+    verify_that!(
+        is_contained_in![eq(2), eq(3), eq(4)].explain_match(&vec![1, 2, 3]),
+        displays_as(eq("whose element #0 does not match any expected elements"))
+    )
+}
+
+#[test]
+fn is_contained_in_explains_missing_elements_in_mismatch() -> Result<()> {
+    verify_that!(
+        is_contained_in![eq(2), eq(3), eq(4), eq(5)].explain_match(&vec![0, 1, 2, 3]),
+        displays_as(eq("whose elements #0, #1 do not match any expected elements"))
+    )
+}
+
+#[test]
+fn is_contained_in_explains_mismatch_due_to_no_graph_matching_found() -> Result<()> {
+    verify_that!(
+        is_contained_in![ge(1), ge(3)].explain_match(&vec![1, 2]),
+        displays_as(eq(indoc!(
+            "
+            which does not have a subset match with the expected elements. The best match found was:
+              Actual element 1 at index 0 matched expected element `is greater than or equal to 1` at index 0.
+              Actual element 2 at index 1 did not match any remaining expected element.
+              Expected element `is greater than or equal to 3` at index 1 did not match any remaining actual element."))
+    ))
+}