Upgrade either to 1.9.0

This project was upgraded with external_updater.
Usage: tools/external_updater/updater.sh update external/rust/crates/either
For more info, check https://cs.android.com/android/platform/superproject/+/main:tools/external_updater/README.md

Test: TreeHugger
Change-Id: I12878bac614d10989a81a028426d5f3a88f8589c
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
index b119b58..3075267 100644
--- a/.cargo_vcs_info.json
+++ b/.cargo_vcs_info.json
@@ -1,6 +1,5 @@
 {
   "git": {
-    "sha1": "b0d9a95738ac536240cf3688f67a863afda81295"
-  },
-  "path_in_vcs": ""
-}
\ No newline at end of file
+    "sha1": "7a60ae82de8309d23d9e3fa55668bf713bd916bf"
+  }
+}
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 905cbf5..37ced3c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -30,6 +30,15 @@
         with:
           toolchain: ${{ matrix.rust }}
 
+      - name: MSRV dependencies
+        if: matrix.rust == '1.36.0'
+        run: |
+          cargo generate-lockfile
+          cargo update -p serde_json --precise 1.0.99
+          cargo update -p serde --precise 1.0.156
+          cargo update -p quote --precise 1.0.30
+          cargo update -p proc-macro2 --precise 1.0.65
+
       - name: Build (no_std)
         run: cargo build --no-default-features
 
diff --git a/Android.bp b/Android.bp
index d563a0e..5cdad27 100644
--- a/Android.bp
+++ b/Android.bp
@@ -42,7 +42,7 @@
     host_supported: true,
     crate_name: "either",
     cargo_env_compat: true,
-    cargo_pkg_version: "1.8.1",
+    cargo_pkg_version: "1.9.0",
     srcs: ["src/lib.rs"],
     test_suites: ["general-tests"],
     auto_gen_config: true,
@@ -62,7 +62,7 @@
     host_supported: true,
     crate_name: "either",
     cargo_env_compat: true,
-    cargo_pkg_version: "1.8.1",
+    cargo_pkg_version: "1.9.0",
     srcs: ["src/lib.rs"],
     edition: "2018",
     features: [
diff --git a/Cargo.toml b/Cargo.toml
index 028ae0e..50ed048 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,46 +3,35 @@
 # 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.
+# 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.
+# If you believe there's an error in this file please file an
+# issue against the rust-lang/cargo repository. If you're
+# editing this file be aware that the upstream Cargo.toml
+# will likely look very different (and much more reasonable)
 
 [package]
 edition = "2018"
-rust-version = "1.36"
 name = "either"
-version = "1.8.1"
+version = "1.9.0"
 authors = ["bluss"]
-description = """
-The enum `Either` with variants `Left` and `Right` is a general purpose sum type with two cases.
-"""
+description = "The enum `Either` with variants `Left` and `Right` is a general purpose sum type with two cases.\n"
 documentation = "https://docs.rs/either/1/"
 readme = "README-crates.io.md"
-keywords = [
-    "data-structure",
-    "no_std",
-]
-categories = [
-    "data-structures",
-    "no-std",
-]
+keywords = ["data-structure", "no_std"]
+categories = ["data-structures", "no-std"]
 license = "MIT OR Apache-2.0"
 repository = "https://github.com/bluss/either"
+[package.metadata.docs.rs]
+features = ["serde"]
 
 [package.metadata.release]
 no-dev-version = true
 tag-name = "{{version}}"
-
-[package.metadata.docs.rs]
-features = ["serde"]
-
 [dependencies.serde]
 version = "1.0"
 features = ["derive"]
 optional = true
-
 [dev-dependencies.serde_json]
 version = "1.0.0"
 
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
index a2768f1..9a7cada 100644
--- a/Cargo.toml.orig
+++ b/Cargo.toml.orig
@@ -1,6 +1,6 @@
 [package]
 name = "either"
-version = "1.8.1"
+version = "1.9.0"
 authors = ["bluss"]
 edition = "2018"
 rust-version = "1.36"
diff --git a/METADATA b/METADATA
index 535787f..c2a2a93 100644
--- a/METADATA
+++ b/METADATA
@@ -1,23 +1,20 @@
 # This project was upgraded with external_updater.
-# Usage: tools/external_updater/updater.sh update rust/crates/either
-# For more info, check https://cs.android.com/android/platform/superproject/+/master:tools/external_updater/README.md
+# Usage: tools/external_updater/updater.sh update external/rust/crates/either
+# For more info, check https://cs.android.com/android/platform/superproject/+/main:tools/external_updater/README.md
 
 name: "either"
 description: "The enum `Either` with variants `Left` and `Right` is a general purpose sum type with two cases."
 third_party {
-  url {
-    type: HOMEPAGE
-    value: "https://crates.io/crates/either"
-  }
-  url {
-    type: ARCHIVE
-    value: "https://static.crates.io/crates/either/either-1.8.1.crate"
-  }
-  version: "1.8.1"
   license_type: NOTICE
   last_upgrade_date {
-    year: 2023
+    year: 2024
     month: 2
-    day: 15
+    day: 1
+  }
+  homepage: "https://crates.io/crates/either"
+  identifier {
+    type: "Archive"
+    value: "https://static.crates.io/crates/either/either-1.9.0.crate"
+    version: "1.9.0"
   }
 }
diff --git a/README.rst b/README.rst
index be27fc1..85ee6c3 100644
--- a/README.rst
+++ b/README.rst
@@ -31,6 +31,10 @@
 Recent Changes
 --------------
 
+- 1.9.0
+
+  - Add new methods ``.map_either()`` and ``.map_either_with()``, by @nasadorian (#82)
+
 - 1.8.1
 
   - Clarified that the multiple licenses are combined with OR.
diff --git a/src/lib.rs b/src/lib.rs
index 9a271c3..d7fbf2c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -348,6 +348,65 @@
         }
     }
 
+    /// Apply the functions `f` and `g` to the `Left` and `Right` variants
+    /// respectively. This is equivalent to
+    /// [bimap](https://hackage.haskell.org/package/bifunctors-5/docs/Data-Bifunctor.html)
+    /// in functional programming.
+    ///
+    /// ```
+    /// use either::*;
+    ///
+    /// let f = |s: String| s.len();
+    /// let g = |u: u8| u.to_string();
+    ///
+    /// let left: Either<String, u8> = Left("loopy".into());
+    /// assert_eq!(left.map_either(f, g), Left(5));
+    ///
+    /// let right: Either<String, u8> = Right(42);
+    /// assert_eq!(right.map_either(f, g), Right("42".into()));
+    /// ```
+    pub fn map_either<F, G, M, S>(self, f: F, g: G) -> Either<M, S>
+    where
+        F: FnOnce(L) -> M,
+        G: FnOnce(R) -> S,
+    {
+        match self {
+            Left(l) => Left(f(l)),
+            Right(r) => Right(g(r)),
+        }
+    }
+
+    /// Similar to [`map_either`], with an added context `ctx` accessible to
+    /// both functions.
+    ///
+    /// ```
+    /// use either::*;
+    ///
+    /// let mut sum = 0;
+    ///
+    /// // Both closures want to update the same value, so pass it as context.
+    /// let mut f = |sum: &mut usize, s: String| { *sum += s.len(); s.to_uppercase() };
+    /// let mut g = |sum: &mut usize, u: usize| { *sum += u; u.to_string() };
+    ///
+    /// let left: Either<String, usize> = Left("loopy".into());
+    /// assert_eq!(left.map_either_with(&mut sum, &mut f, &mut g), Left("LOOPY".into()));
+    ///
+    /// let right: Either<String, usize> = Right(42);
+    /// assert_eq!(right.map_either_with(&mut sum, &mut f, &mut g), Right("42".into()));
+    ///
+    /// assert_eq!(sum, 47);
+    /// ```
+    pub fn map_either_with<Ctx, F, G, M, S>(self, ctx: Ctx, f: F, g: G) -> Either<M, S>
+    where
+        F: FnOnce(Ctx, L) -> M,
+        G: FnOnce(Ctx, R) -> S,
+    {
+        match self {
+            Left(l) => Left(f(ctx, l)),
+            Right(r) => Right(g(ctx, r)),
+        }
+    }
+
     /// Apply one of two functions depending on contents, unifying their result. If the value is
     /// `Left(L)` then the first function `f` is applied; if it is `Right(R)` then the second
     /// function `g` is applied.