Repository: clap-rs/clap
Pull request #4084 (2022-08-16 16:54:11)

fix!: Require/default conditional APIs are more explicit
This helps with
- API cleanup by not having ambigious `None`, see #950
- Removes ambiguity with `None` when using owned/borrowed types for
  #1041


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 441377e0..954b094c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -43,6 +43,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 - Looking up a group in `ArgMatches` now returns the arg `Id`s, rather than the values.
 - Various `Arg`, `Command`, and `ArgGroup` calls were switched from accepting `&[]` to `[]` via `IntoIterator`
 - `Arg::short_aliases` and other builder functions that took `&[]` need the `&` dropped
+- Changed `Arg::requires_ifs` and `Arg::default_value*_ifs*` to taking an `ArgPredicate`, removing ambiguity with `None` when accepting owned and borrowed types
+- Replaced `Arg::requires_all` with `Arg::requires_ifs` now that it takes an `ArgPredicate`
 - *(help)* Make `DeriveDisplayOrder` the default and removed the setting.  To sort help, set `next_display_order(None)` (#2808)
 - *(help)* Subcommand display order respects `Command::next_display_order` instead of `DeriveDisplayOrder` and using its own initial display order value (#2808)
 - *(env)* Parse `--help` and `--version` like any `ArgAction::SetTrue` flag (#3776)
@@ -67,6 +69,7 @@ MSRV is now 1.60.0
 - Changed the default type of `allow_external_subcommands` from `String` to `OsString`
 - `Arg::default_missing_value` now applies per occurrence rather than if a value is missing across all occurrences
 - `arg!(--long [value])` to accept `0..=1` per occurrence rather than across all occurrences, making it safe to use with `ArgAction::Append`
+- Allow `OsStr`s for `Arg::{required_if_eq,required_if_eq_any,required_if_eq_all}`
 - *(assert)* Always enforce that version is specified when the `ArgAction::Version` is used
 - *(assert)* Add missing `#[track_caller]`s to make it easier to debug asserts
 - *(assert)* Ensure `overrides_with` IDs are valid
diff --git a/src/_derive/mod.rs b/src/_derive/mod.rs
index 158d936a..f271e01f 100644
--- a/src/_derive/mod.rs
+++ b/src/_derive/mod.rs
@@ -185,7 +185,7 @@
 //!   - When not present: case-converted field name is used
 //! - `value_parser [= <expr>]`: [`Arg::value_parser`][crate::Arg::value_parser]
 //!   - When not present: will auto-select an implementation based on the field type using
-//!     [`value_parser!`][crate::value_parser!]
+//!     [`value_parser!][crate::value_parser!]
 //!   - To register a custom type's [`ValueParser`][crate::builder::ValueParser], implement [`ValueParserFactory`][crate::builder::ValueParserFactory]
 //! - `action [= <expr>]`: [`Arg::action`][crate::Arg::action]
 //!   - When not present: will auto-select an action based on the field type
diff --git a/src/builder/arg.rs b/src/builder/arg.rs
index 0be1c52d..ffebc3d1 100644
--- a/src/builder/arg.rs
+++ b/src/builder/arg.rs
@@ -1,13 +1,14 @@
 // Std
+#[cfg(feature = "env")]
+use std::env;
 use std::{
     borrow::Cow,
     cmp::{Ord, Ordering},
     ffi::OsStr,
+    ffi::OsString,
     fmt::{self, Display, Formatter},
     str,
 };
-#[cfg(feature = "env")]
-use std::{env, ffi::OsString};
 
 // Internal
 use super::{ArgFlags, ArgSettings};
@@ -58,9 +59,9 @@ pub struct Arg<'help> {
     pub(crate) settings: ArgFlags,
     pub(crate) overrides: Vec<Id>,
     pub(crate) groups: Vec<Id>,
-    pub(crate) requires: Vec<(ArgPredicate<'help>, Id)>,
-    pub(crate) r_ifs: Vec<(Id, &'help str)>,
-    pub(crate) r_ifs_all: Vec<(Id, &'help str)>,
+    pub(crate) requires: Vec<(ArgPredicate, Id)>,
+    pub(crate) r_ifs: Vec<(Id, OsString)>,
+    pub(crate) r_ifs_all: Vec<(Id, OsString)>,
     pub(crate) r_unless: Vec<Id>,
     pub(crate) r_unless_all: Vec<Id>,
     pub(crate) short: Option<char>,
@@ -72,7 +73,7 @@ pub struct Arg<'help> {
     pub(crate) num_vals: Option<ValueRange>,
     pub(crate) val_delim: Option<char>,
     pub(crate) default_vals: Vec<&'help OsStr>,
-    pub(crate) default_vals_ifs: Vec<(Id, ArgPredicate<'help>, Option<&'help OsStr>)>,
+    pub(crate) default_vals_ifs: Vec<(Id, ArgPredicate, Option<&'help OsStr>)>,
     pub(crate) default_missing_vals: Vec<&'help OsStr>,
     #[cfg(feature = "env")]
     pub(crate) env: Option<(&'help OsStr, Option<OsString>)>,
@@ -2501,9 +2502,6 @@ impl<'help> Arg<'help> {
 
     /// Specifies the value of the argument if `arg` has been used at runtime.
     ///
-    /// If `val` is set to `None`, `arg` only needs to be present. If `val` is set to `"some-val"`
-    /// then `arg` must be present at runtime **and** have the value `val`.
-    ///
     /// If `default` is set to `None`, `default_value` will be removed.
     ///
     /// **NOTE:** This setting is perfectly compatible with [`Arg::default_value`] but slightly
@@ -2521,13 +2519,14 @@ impl<'help> Arg<'help> {
     ///
     /// ```rust
     /// # use clap::{Command, Arg, ArgAction};
+    /// # use clap::builder::{ArgPredicate};
     /// let m = Command::new("prog")
     ///     .arg(Arg::new("flag")
     ///         .long("flag")
     ///         .action(ArgAction::SetTrue))
     ///     .arg(Arg::new("other")
     ///         .long("other")
-    ///         .default_value_if("flag", None, Some("default")))
+    ///         .default_value_if("flag", ArgPredicate::IsPresent, Some("default")))
     ///     .get_matches_from(vec![
     ///         "prog", "--flag"
     ///     ]);
@@ -2545,7 +2544,7 @@ impl<'help> Arg<'help> {
     ///         .action(ArgAction::SetTrue))
     ///     .arg(Arg::new("other")
     ///         .long("other")
-    ///         .default_value_if("flag", Some("true"), Some("default")))
+    ///         .default_value_if("flag", "true", Some("default")))
     ///     .get_matches_from(vec![
     ///         "prog"
     ///     ]);
@@ -2563,7 +2562,7 @@ impl<'help> Arg<'help> {
     ///         .long("opt"))
     ///     .arg(Arg::new("other")
     ///         .long("other")
-    ///         .default_value_if("opt", Some("special"), Some("default")))
+    ///         .default_value_if("opt", "special", Some("default")))
     ///     .get_matches_from(vec![
     ///         "prog", "--opt", "special"
     ///     ]);
@@ -2582,7 +2581,7 @@ impl<'help> Arg<'help> {
     ///         .long("opt"))
     ///     .arg(Arg::new("other")
     ///         .long("other")
-    ///         .default_value_if("opt", Some("special"), Some("default")))
+    ///         .default_value_if("opt", "special", Some("default")))
     ///     .get_matches_from(vec![
     ///         "prog", "--opt", "hahaha"
     ///     ]);
@@ -2602,7 +2601,7 @@ impl<'help> Arg<'help> {
     ///     .arg(Arg::new("other")
     ///         .long("other")
     ///         .default_value("default")
-    ///         .default_value_if("flag", Some("true"), None))
+    ///         .default_value_if("flag", "true", None))
     ///     .get_matches_from(vec![
     ///         "prog", "--flag"
     ///     ]);
@@ -2615,10 +2614,10 @@ impl<'help> Arg<'help> {
     pub fn default_value_if(
         self,
         arg_id: impl Into<Id>,
-        val: Option<&'help str>,
+        val: impl Into<ArgPredicate>,
         default: Option<&'help str>,
     ) -> Self {
-        self.default_value_if_os(arg_id, val.map(OsStr::new), default.map(OsStr::new))
+        self.default_value_if_os(arg_id, val.into(), default.map(OsStr::new))
     }
 
     /// Provides a conditional default value in the exact same manner as [`Arg::default_value_if`]
@@ -2630,7 +2629,7 @@ impl<'help> Arg<'help> {
     pub fn default_value_if_os(
         mut self,
         arg_id: impl Into<Id>,
-        val: Option<&'help OsStr>,
+        val: impl Into<ArgPredicate>,
         default: Option<&'help OsStr>,
     ) -> Self {
         self.default_vals_ifs
@@ -2661,8 +2660,8 @@ impl<'help> Arg<'help> {
     ///     .arg(Arg::new("other")
     ///         .long("other")
     ///         .default_value_ifs([
-    ///             ("flag", Some("true"), Some("default")),
-    ///             ("opt", Some("channal"), Some("chan")),
+    ///             ("flag", "true", Some("default")),
+    ///             ("opt", "channal", Some("chan")),
     ///         ]))
     ///     .get_matches_from(vec![
     ///         "prog", "--opt", "channal"
@@ -2682,8 +2681,8 @@ impl<'help> Arg<'help> {
     ///     .arg(Arg::new("other")
     ///         .long("other")
     ///         .default_value_ifs([
-    ///             ("flag", Some("true"), Some("default")),
-    ///             ("opt", Some("channal"), Some("chan")),
+    ///             ("flag", "true", Some("default")),
+    ///             ("opt", "channal", Some("chan")),
     ///         ]))
     ///     .get_matches_from(vec![
     ///         "prog"
@@ -2697,6 +2696,7 @@ impl<'help> Arg<'help> {
     ///
     /// ```rust
     /// # use clap::{Command, Arg, ArgAction};
+    /// # use clap::builder::ArgPredicate;
     /// let m = Command::new("prog")
     ///     .arg(Arg::new("flag")
     ///         .long("flag")
@@ -2707,8 +2707,8 @@ impl<'help> Arg<'help> {
     ///     .arg(Arg::new("other")
     ///         .long("other")
     ///         .default_value_ifs([
-    ///             ("flag", None, Some("default")),
-    ///             ("opt", Some("channal"), Some("chan")),
+    ///             ("flag", ArgPredicate::IsPresent, Some("default")),
+    ///             ("opt", ArgPredicate::Equals("channal".into()), Some("chan")),
     ///         ]))
     ///     .get_matches_from(vec![
     ///         "prog", "--opt", "channal", "--flag"
@@ -2721,10 +2721,10 @@ impl<'help> Arg<'help> {
     #[must_use]
     pub fn default_value_ifs(
         mut self,
-        ifs: impl IntoIterator<Item = (impl Into<Id>, Option<&'help str>, Option<&'help str>)>,
+        ifs: impl IntoIterator<Item = (impl Into<Id>, impl Into<ArgPredicate>, Option<&'help str>)>,
     ) -> Self {
         for (arg, val, default) in ifs {
-            self = self.default_value_if_os(arg, val.map(OsStr::new), default.map(OsStr::new));
+            self = self.default_value_if_os(arg, val, default.map(OsStr::new));
         }
         self
     }
@@ -2737,7 +2737,7 @@ impl<'help> Arg<'help> {
     #[must_use]
     pub fn default_value_ifs_os(
         mut self,
-        ifs: impl IntoIterator<Item = (impl Into<Id>, Option<&'help OsStr>, Option<&'help OsStr>)>,
+        ifs: impl IntoIterator<Item = (impl Into<Id>, impl Into<ArgPredicate>, Option<&'help OsStr>)>,
     ) -> Self {
         for (arg, val, default) in ifs {
             self = self.default_value_if_os(arg.into(), val, default);
@@ -3038,8 +3038,8 @@ impl<'help> Arg<'help> {
     /// [Conflicting]: Arg::conflicts_with()
     /// [required]: Arg::required()
     #[must_use]
-    pub fn required_if_eq(mut self, arg_id: impl Into<Id>, val: &'help str) -> Self {
-        self.r_ifs.push((arg_id.into(), val));
+    pub fn required_if_eq(mut self, arg_id: impl Into<Id>, val: impl Into<OsString>) -> Self {
+        self.r_ifs.push((arg_id.into(), val.into()));
         self
     }
 
@@ -3119,10 +3119,10 @@ impl<'help> Arg<'help> {
     #[must_use]
     pub fn required_if_eq_any(
         mut self,
-        ifs: impl IntoIterator<Item = (impl Into<Id>, &'help str)>,
+        ifs: impl IntoIterator<Item = (impl Into<Id>, impl Into<OsString>)>,
     ) -> Self {
         self.r_ifs
-            .extend(ifs.into_iter().map(|(id, val)| (id.into(), val)));
+            .extend(ifs.into_iter().map(|(id, val)| (id.into(), val.into())));
         self
     }
 
@@ -3200,17 +3200,17 @@ impl<'help> Arg<'help> {
     #[must_use]
     pub fn required_if_eq_all(
         mut self,
-        ifs: impl IntoIterator<Item = (impl Into<Id>, &'help str)>,
+        ifs: impl IntoIterator<Item = (impl Into<Id>, impl Into<OsString>)>,
     ) -> Self {
         self.r_ifs_all
-            .extend(ifs.into_iter().map(|(id, val)| (id.into(), val)));
+            .extend(ifs.into_iter().map(|(id, val)| (id.into(), val.into())));
         self
     }
 
-    /// Require another argument if this arg was present at runtime and its value equals to `val`.
+    /// Require another argument if this arg matches the [`ArgPredicate`]
     ///
     /// This method takes `value, another_arg` pair. At runtime, clap will check
-    /// if this arg (`self`) is present and its value equals to `val`.
+    /// if this arg (`self`) matches the [`ArgPredicate`].
     /// If it does, `another_arg` will be marked as required.
     ///
     /// # Examples
@@ -3263,15 +3263,15 @@ impl<'help> Arg<'help> {
     /// [Conflicting]: Arg::conflicts_with()
     /// [override]: Arg::overrides_with()
     #[must_use]
-    pub fn requires_if(mut self, val: &'help str, arg_id: impl Into<Id>) -> Self {
-        self.requires
-            .push((ArgPredicate::Equals(OsStr::new(val)), arg_id.into()));
+    pub fn requires_if(mut self, val: impl Into<ArgPredicate>, arg_id: impl Into<Id>) -> Self {
+        self.requires.push((val.into(), arg_id.into()));
         self
     }
 
     /// Allows multiple conditional requirements.
     ///
-    /// The requirement will only become valid if this arg's value equals `val`.
+    /// The requirement will only become valid if this arg's value matches the
+    /// [`ArgPredicate`].
     ///
     /// # Examples
     ///
@@ -3310,66 +3310,19 @@ impl<'help> Arg<'help> {
     /// assert!(res.is_err()); // We  used --config=special.conf so --option <val> is required
     /// assert_eq!(res.unwrap_err().kind(), ErrorKind::MissingRequiredArgument);
     /// ```
-    /// [`Arg::requires(name)`]: Arg::requires()
-    /// [Conflicting]: Arg::conflicts_with()
-    /// [override]: Arg::overrides_with()
-    #[must_use]
-    pub fn requires_ifs(
-        mut self,
-        ifs: impl IntoIterator<Item = (&'help str, impl Into<Id>)>,
-    ) -> Self {
-        self.requires.extend(
-            ifs.into_iter()
-                .map(|(val, arg)| (ArgPredicate::Equals(OsStr::new(val)), arg.into())),
-        );
-        self
-    }
-
-    /// Require these arguments names when this one is present
-    ///
-    /// i.e. when using this argument, the following arguments *must* be present.
-    ///
-    /// **NOTE:** [Conflicting] rules and [override] rules take precedence over being required
-    /// by default.
-    ///
-    /// # Examples
-    ///
-    /// ```rust
-    /// # use clap::Arg;
-    /// Arg::new("config")
-    ///     .requires_all(["input", "output"])
-    /// # ;
-    /// ```
     ///
-    /// Setting `Arg::requires_all([arg, arg2])` requires that all the arguments be used at
-    /// runtime if the defining argument is used. If the defining argument isn't used, the other
-    /// argument isn't required
+    /// Setting `Arg::requires_ifs` with [`ArgPredicate::IsPresent`] and *not* supplying all the
+    /// arguments is an error.
     ///
     /// ```rust
-    /// # use clap::{Command, Arg, ArgAction};
+    /// # use clap::{Command, Arg, error::ErrorKind, ArgAction, builder::ArgPredicate};
     /// let res = Command::new("prog")
     ///     .arg(Arg::new("cfg")
     ///         .action(ArgAction::Set)
-    ///         .requires("input")
-    ///         .long("config"))
-    ///     .arg(Arg::new("input"))
-    ///     .arg(Arg::new("output"))
-    ///     .try_get_matches_from(vec![
-    ///         "prog"
-    ///     ]);
-    ///
-    /// assert!(res.is_ok()); // We didn't use cfg, so input and output weren't required
-    /// ```
-    ///
-    /// Setting `Arg::requires_all([arg, arg2])` and *not* supplying all the arguments is an
-    /// error.
-    ///
-    /// ```rust
-    /// # use clap::{Command, Arg, error::ErrorKind, ArgAction};
-    /// let res = Command::new("prog")
-    ///     .arg(Arg::new("cfg")
-    ///         .action(ArgAction::Set)
-    ///         .requires_all(["input", "output"])
+    ///         .requires_ifs([
+    ///             (ArgPredicate::IsPresent, "input"),
+    ///             (ArgPredicate::IsPresent, "output"),
+    ///         ])
     ///         .long("config"))
     ///     .arg(Arg::new("input"))
     ///     .arg(Arg::new("output"))
@@ -3381,15 +3334,17 @@ impl<'help> Arg<'help> {
     /// // We didn't use output
     /// assert_eq!(res.unwrap_err().kind(), ErrorKind::MissingRequiredArgument);
     /// ```
+    ///
+    /// [`Arg::requires(name)`]: Arg::requires()
     /// [Conflicting]: Arg::conflicts_with()
     /// [override]: Arg::overrides_with()
     #[must_use]
-    pub fn requires_all(mut self, names: impl IntoIterator<Item = impl Into<Id>>) -> Self {
-        self.requires.extend(
-            names
-                .into_iter()
-                .map(|s| (ArgPredicate::IsPresent, s.into())),
-        );
+    pub fn requires_ifs(
+        mut self,
+        ifs: impl IntoIterator<Item = (impl Into<ArgPredicate>, impl Into<Id>)>,
+    ) -> Self {
+        self.requires
+            .extend(ifs.into_iter().map(|(val, arg)| (val.into(), arg.into())));
         self
     }
 
diff --git a/src/builder/arg_group.rs b/src/builder/arg_group.rs
index 64efeb95..32dbc97e 100644
--- a/src/builder/arg_group.rs
+++ b/src/builder/arg_group.rs
@@ -355,7 +355,7 @@ impl ArgGroup {
     /// assert_eq!(err.kind(), ErrorKind::MissingRequiredArgument);
     /// ```
     /// [required group]: ArgGroup::required()
-    /// [argument requirement rules]: crate::Arg::requires_all()
+    /// [argument requirement rules]: crate::Arg::requires_ifs()
     #[must_use]
     pub fn requires_all(mut self, ns: impl IntoIterator<Item = impl Into<Id>>) -> Self {
         for n in ns {
diff --git a/src/builder/arg_predicate.rs b/src/builder/arg_predicate.rs
index 58eb5494..34768659 100644
--- a/src/builder/arg_predicate.rs
+++ b/src/builder/arg_predicate.rs
@@ -1,14 +1,18 @@
-#[derive(Copy, Clone, Debug, PartialEq, Eq)]
-pub(crate) enum ArgPredicate<'help> {
+use crate::OsStr;
+
+/// Operations to perform on argument values
+///
+/// These do not apply to [`ValueSource::DefaultValue`][crate::parser::ValueSource::DefaultValue]
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum ArgPredicate {
+    /// Is the argument present?
     IsPresent,
-    Equals(&'help std::ffi::OsStr),
+    /// Does the argument match the specified value?
+    Equals(OsStr),
 }
 
-impl<'help> From<Option<&'help std::ffi::OsStr>> for ArgPredicate<'help> {
-    fn from(other: Option<&'help std::ffi::OsStr>) -> Self {
-        match other {
-            Some(other) => Self::Equals(other),
-            None => Self::IsPresent,
-        }
+impl<S: Into<OsStr>> From<S> for ArgPredicate {
+    fn from(other: S) -> Self {
+        Self::Equals(other.into())
     }
 }
diff --git a/src/builder/command.rs b/src/builder/command.rs
index cf657857..925c2995 100644
--- a/src/builder/command.rs
+++ b/src/builder/command.rs
@@ -4333,7 +4333,7 @@ impl<'help> Command<'help> {
 
     pub(crate) fn unroll_arg_requires<F>(&self, func: F, arg: &Id) -> Vec<Id>
     where
-        F: Fn(&(ArgPredicate<'_>, Id)) -> Option<Id>,
+        F: Fn(&(ArgPredicate, Id)) -> Option<Id>,
     {
         let mut processed = vec![];
         let mut r_vec = vec![arg];
diff --git a/src/builder/mod.rs b/src/builder/mod.rs
index a951c6ec..a147f1eb 100644
--- a/src/builder/mod.rs
+++ b/src/builder/mod.rs
@@ -21,6 +21,7 @@ mod tests;
 pub use action::ArgAction;
 pub use arg::Arg;
 pub use arg_group::ArgGroup;
+pub use arg_predicate::ArgPredicate;
 pub use command::Command;
 pub use possible_value::PossibleValue;
 pub use range::ValueRange;
@@ -45,5 +46,4 @@ pub use value_parser::_AnonymousValueParser;
 
 pub(crate) use action::CountType;
 pub(crate) use arg::render_arg_val;
-pub(crate) use arg_predicate::ArgPredicate;
 pub(crate) use arg_settings::{ArgFlags, ArgSettings};
diff --git a/src/lib.rs b/src/lib.rs
index 0059ca39..cf87ddd0 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -109,6 +109,8 @@ pub use crate::util::color::ColorChoice;
 #[allow(unused_imports)]
 pub(crate) use crate::util::color::ColorChoice;
 pub use crate::util::Id;
+pub(crate) use crate::util::OsStr;
+pub(crate) use crate::util::Str;
 
 pub use crate::derive::{Args, CommandFactory, FromArgMatches, Parser, Subcommand, ValueEnum};
 
diff --git a/src/output/usage.rs b/src/output/usage.rs
index 0235eff5..db07e873 100644
--- a/src/output/usage.rs
+++ b/src/output/usage.rs
@@ -359,11 +359,11 @@ impl<'help, 'cmd> Usage<'help, 'cmd> {
         };
 
         for a in required.iter() {
-            let is_relevant = |(val, req_arg): &(ArgPredicate<'_>, Id)| -> Option<Id> {
+            let is_relevant = |(val, req_arg): &(ArgPredicate, Id)| -> Option<Id> {
                 let required = match val {
                     ArgPredicate::Equals(_) => {
                         if let Some(matcher) = matcher {
-                            matcher.check_explicit(a, *val)
+                            matcher.check_explicit(a, val)
                         } else {
                             false
                         }
@@ -395,7 +395,7 @@ impl<'help, 'cmd> Usage<'help, 'cmd> {
         for req in unrolled_reqs.iter().chain(incls.iter()) {
             if let Some(arg) = self.cmd.find(req) {
                 let is_present = matcher
-                    .map(|m| m.check_explicit(req, ArgPredicate::IsPresent))
+                    .map(|m| m.check_explicit(req, &ArgPredicate::IsPresent))
                     .unwrap_or(false);
                 debug!(
                     "Usage::get_required_usage_from:iter:{:?} arg is_present={}",
@@ -417,7 +417,7 @@ impl<'help, 'cmd> Usage<'help, 'cmd> {
                     .map(|m| {
                         group_members
                             .iter()
-                            .any(|arg| m.check_explicit(arg, ArgPredicate::IsPresent))
+                            .any(|arg| m.check_explicit(arg, &ArgPredicate::IsPresent))
                     })
                     .unwrap_or(false);
                 debug!(
diff --git a/src/parser/arg_matcher.rs b/src/parser/arg_matcher.rs
index 6b68e641..433b1425 100644
--- a/src/parser/arg_matcher.rs
+++ b/src/parser/arg_matcher.rs
@@ -125,7 +125,7 @@ impl ArgMatcher {
         self.matches.subcommand_name()
     }
 
-    pub(crate) fn check_explicit<'a>(&self, arg: &Id, predicate: ArgPredicate<'a>) -> bool {
+    pub(crate) fn check_explicit(&self, arg: &Id, predicate: &ArgPredicate) -> bool {
         self.get(arg).map_or(false, |a| a.check_explicit(predicate))
     }
 
diff --git a/src/parser/matches/matched_arg.rs b/src/parser/matches/matched_arg.rs
index 74bac990..22d5b414 100644
--- a/src/parser/matches/matched_arg.rs
+++ b/src/parser/matches/matched_arg.rs
@@ -129,7 +129,7 @@ impl MatchedArg {
         self.vals.iter().flatten().count() == 0
     }
 
-    pub(crate) fn check_explicit(&self, predicate: ArgPredicate) -> bool {
+    pub(crate) fn check_explicit(&self, predicate: &ArgPredicate) -> bool {
         if self.source == Some(ValueSource::DefaultValue) {
             return false;
         }
diff --git a/src/parser/parser.rs b/src/parser/parser.rs
index d2355210..1eea0d17 100644
--- a/src/parser/parser.rs
+++ b/src/parser/parser.rs
@@ -778,7 +778,7 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
                 let used: Vec<Id> = matcher
                     .arg_ids()
                     .filter(|arg_id| {
-                        matcher.check_explicit(arg_id, crate::builder::ArgPredicate::IsPresent)
+                        matcher.check_explicit(arg_id, &crate::builder::ArgPredicate::IsPresent)
                     })
                     .filter(|&n| {
                         self.cmd
@@ -1513,7 +1513,7 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
         let used: Vec<Id> = matcher
             .arg_ids()
             .filter(|arg_id| {
-                matcher.check_explicit(arg_id, crate::builder::ArgPredicate::IsPresent)
+                matcher.check_explicit(arg_id, &crate::builder::ArgPredicate::IsPresent)
             })
             .filter(|n| self.cmd.find(n).map_or(true, |a| !a.is_hide_set()))
             .cloned()
diff --git a/src/parser/validator.rs b/src/parser/validator.rs
index 3936a5be..f29804ed 100644
--- a/src/parser/validator.rs
+++ b/src/parser/validator.rs
@@ -55,7 +55,7 @@ impl<'help, 'cmd> Validator<'help, 'cmd> {
         if !has_subcmd && self.cmd.is_arg_required_else_help_set() {
             let num_user_values = matcher
                 .arg_ids()
-                .filter(|arg_id| matcher.check_explicit(arg_id, ArgPredicate::IsPresent))
+                .filter(|arg_id| matcher.check_explicit(arg_id, &ArgPredicate::IsPresent))
                 .count();
             if num_user_values == 0 {
                 let message = self.cmd.write_help_err(false, Stream::Stderr)?;
@@ -95,7 +95,7 @@ impl<'help, 'cmd> Validator<'help, 'cmd> {
 
         for arg_id in matcher
             .arg_ids()
-            .filter(|arg_id| matcher.check_explicit(arg_id, ArgPredicate::IsPresent))
+            .filter(|arg_id| matcher.check_explicit(arg_id, &ArgPredicate::IsPresent))
             .filter(|arg_id| self.cmd.find(arg_id).is_some())
         {
             debug!("Validator::validate_conflicts::iter: id={:?}", arg_id);
@@ -111,7 +111,7 @@ impl<'help, 'cmd> Validator<'help, 'cmd> {
         let args_count = matcher
             .arg_ids()
             .filter(|arg_id| {
-                matcher.check_explicit(arg_id, crate::builder::ArgPredicate::IsPresent)
+                matcher.check_explicit(arg_id, &crate::builder::ArgPredicate::IsPresent)
             })
             .count();
         if args_count <= 1 {
@@ -122,7 +122,7 @@ impl<'help, 'cmd> Validator<'help, 'cmd> {
         matcher
             .arg_ids()
             .filter(|arg_id| {
-                matcher.check_explicit(arg_id, crate::builder::ArgPredicate::IsPresent)
+                matcher.check_explicit(arg_id, &crate::builder::ArgPredicate::IsPresent)
             })
             .filter_map(|name| {
                 debug!("Validator::validate_exclusive:iter:{:?}", name);
@@ -186,7 +186,7 @@ impl<'help, 'cmd> Validator<'help, 'cmd> {
     fn build_conflict_err_usage(&self, matcher: &ArgMatcher, conflicting_keys: &[Id]) -> String {
         let used_filtered: Vec<Id> = matcher
             .arg_ids()
-            .filter(|arg_id| matcher.check_explicit(arg_id, ArgPredicate::IsPresent))
+            .filter(|arg_id| matcher.check_explicit(arg_id, &ArgPredicate::IsPresent))
             .filter(|n| {
                 // Filter out the args we don't want to specify.
                 self.cmd.find(n).map_or(true, |a| !a.is_hide_set())
@@ -211,12 +211,12 @@ impl<'help, 'cmd> Validator<'help, 'cmd> {
         debug!("Validator::gather_requires");
         for name in matcher
             .arg_ids()
-            .filter(|arg_id| matcher.check_explicit(arg_id, ArgPredicate::IsPresent))
+            .filter(|arg_id| matcher.check_explicit(arg_id, &ArgPredicate::IsPresent))
         {
             debug!("Validator::gather_requires:iter:{:?}", name);
             if let Some(arg) = self.cmd.find(name) {
-                let is_relevant = |(val, req_arg): &(ArgPredicate<'_>, Id)| -> Option<Id> {
-                    let required = matcher.check_explicit(&arg.id, *val);
+                let is_relevant = |(val, req_arg): &(ArgPredicate, Id)| -> Option<Id> {
+                    let required = matcher.check_explicit(&arg.id, val);
                     required.then(|| req_arg.clone())
                 };
 
@@ -242,7 +242,7 @@ impl<'help, 'cmd> Validator<'help, 'cmd> {
 
         let is_exclusive_present = matcher
             .arg_ids()
-            .filter(|arg_id| matcher.check_explicit(arg_id, ArgPredicate::IsPresent))
+            .filter(|arg_id| matcher.check_explicit(arg_id, &ArgPredicate::IsPresent))
             .any(|id| {
                 self.cmd
                     .find(id)
@@ -257,7 +257,7 @@ impl<'help, 'cmd> Validator<'help, 'cmd> {
         for arg_or_group in self
             .required
             .iter()
-            .filter(|r| !matcher.check_explicit(r, ArgPredicate::IsPresent))
+            .filter(|r| !matcher.check_explicit(r, &ArgPredicate::IsPresent))
         {
             debug!("Validator::validate_required:iter:aog={:?}", arg_or_group);
             if let Some(arg) = self.cmd.find(arg_or_group) {
@@ -271,7 +271,7 @@ impl<'help, 'cmd> Validator<'help, 'cmd> {
                     .cmd
                     .unroll_args_in_group(&group.id)
                     .iter()
-                    .any(|a| matcher.check_explicit(a, ArgPredicate::IsPresent))
+                    .any(|a| matcher.check_explicit(a, &ArgPredicate::IsPresent))
                 {
                     return self.missing_required_error(matcher, vec![]);
                 }
@@ -281,19 +281,19 @@ impl<'help, 'cmd> Validator<'help, 'cmd> {
         // Validate the conditionally required args
         for a in self.cmd.get_arguments() {
             for (other, val) in &a.r_ifs {
-                if matcher.check_explicit(other, ArgPredicate::Equals(std::ffi::OsStr::new(*val)))
-                    && !matcher.check_explicit(&a.id, ArgPredicate::IsPresent)
+                if matcher.check_explicit(other, &ArgPredicate::Equals(val.into()))
+                    && !matcher.check_explicit(&a.id, &ArgPredicate::IsPresent)
                 {
                     return self.missing_required_error(matcher, vec![a.id.clone()]);
                 }
             }
 
             let match_all = a.r_ifs_all.iter().all(|(other, val)| {
-                matcher.check_explicit(other, ArgPredicate::Equals(std::ffi::OsStr::new(*val)))
+                matcher.check_explicit(other, &ArgPredicate::Equals(val.into()))
             });
             if match_all
                 && !a.r_ifs_all.is_empty()
-                && !matcher.check_explicit(&a.id, ArgPredicate::IsPresent)
+                && !matcher.check_explicit(&a.id, &ArgPredicate::IsPresent)
             {
                 return self.missing_required_error(matcher, vec![a.id.clone()]);
             }
@@ -322,7 +322,7 @@ impl<'help, 'cmd> Validator<'help, 'cmd> {
             .get_arguments()
             .filter(|&a| {
                 (!a.r_unless.is_empty() || !a.r_unless_all.is_empty())
-                    && !matcher.check_explicit(&a.id, ArgPredicate::IsPresent)
+                    && !matcher.check_explicit(&a.id, &ArgPredicate::IsPresent)
                     && self.fails_arg_required_unless(a, matcher)
             })
             .map(|a| a.id.clone())
@@ -337,7 +337,7 @@ impl<'help, 'cmd> Validator<'help, 'cmd> {
     // Failing a required unless means, the arg's "unless" wasn't present, and neither were they
     fn fails_arg_required_unless(&self, a: &Arg<'help>, matcher: &ArgMatcher) -> bool {
         debug!("Validator::fails_arg_required_unless: a={:?}", a.get_id());
-        let exists = |id| matcher.check_explicit(id, ArgPredicate::IsPresent);
+        let exists = |id| matcher.check_explicit(id, &ArgPredicate::IsPresent);
 
         (a.r_unless_all.is_empty() || !a.r_unless_all.iter().all(exists))
             && !a.r_unless.iter().any(exists)
@@ -365,7 +365,7 @@ impl<'help, 'cmd> Validator<'help, 'cmd> {
 
         let used: Vec<Id> = matcher
             .arg_ids()
-            .filter(|arg_id| matcher.check_explicit(arg_id, ArgPredicate::IsPresent))
+            .filter(|arg_id| matcher.check_explicit(arg_id, &ArgPredicate::IsPresent))
             .filter(|n| {
                 // Filter out the args we don't want to specify.
                 self.cmd.find(n).map_or(true, |a| !a.is_hide_set())
@@ -397,7 +397,7 @@ impl Conflicts {
         let mut conflicts = Vec::new();
         for other_arg_id in matcher
             .arg_ids()
-            .filter(|arg_id| matcher.check_explicit(arg_id, ArgPredicate::IsPresent))
+            .filter(|arg_id| matcher.check_explicit(arg_id, &ArgPredicate::IsPresent))
         {
             if arg_id == other_arg_id {
                 continue;
diff --git a/src/util/id.rs b/src/util/id.rs
index b1551492..0827b774 100644
--- a/src/util/id.rs
+++ b/src/util/id.rs
@@ -1,69 +1,67 @@
+use crate::Str;
+
 /// [`Arg`][crate::Arg] or [`ArgGroup`][crate::ArgGroup] identifier
 ///
 /// This is used for accessing the value in [`ArgMatches`][crate::ArgMatches] or defining
 /// relationships between `Arg`s and `ArgGroup`s with functions like
 /// [`Arg::conflicts_with`][crate::Arg::conflicts_with].
 #[derive(Default, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
-pub struct Id {
-    name: Inner,
-}
+pub struct Id(Str);
 
 impl Id {
     pub(crate) const HELP: Self = Self::from_static_ref("help");
     pub(crate) const VERSION: Self = Self::from_static_ref("version");
     pub(crate) const EXTERNAL: Self = Self::from_static_ref("");
 
-    fn from_string(name: String) -> Self {
-        Self {
-            name: Inner::Owned(name.into_boxed_str()),
-        }
-    }
-
-    fn from_ref(name: &str) -> Self {
-        Self {
-            name: Inner::Owned(Box::from(name)),
-        }
-    }
-
     const fn from_static_ref(name: &'static str) -> Self {
-        Self {
-            name: Inner::Static(name),
-        }
+        Self(Str::from_static_ref(name))
     }
 
     /// Get the raw string of the `Id`
     pub fn as_str(&self) -> &str {
-        self.name.as_str()
+        self.0.as_str()
     }
 }
 
-impl<'s> From<&'s Id> for Id {
-    fn from(id: &'s Id) -> Self {
+impl From<&'_ Id> for Id {
+    fn from(id: &'_ Id) -> Self {
         id.clone()
     }
 }
 
-impl From<String> for Id {
-    fn from(name: String) -> Self {
-        Self::from_string(name)
+impl From<Str> for Id {
+    fn from(name: Str) -> Self {
+        Self(name)
+    }
+}
+
+impl From<&'_ Str> for Id {
+    fn from(name: &'_ Str) -> Self {
+        Self(name.into())
+    }
+}
+
+impl From<std::string::String> for Id {
+    fn from(name: std::string::String) -> Self {
+        Self(name.into())
     }
 }
 
-impl From<&'_ String> for Id {
-    fn from(name: &'_ String) -> Self {
-        Self::from_ref(name.as_str())
+impl From<&'_ std::string::String> for Id {
+    fn from(name: &'_ std::string::String) -> Self {
+        Self(name.into())
     }
 }
 
 impl From<&'static str> for Id {
     fn from(name: &'static str) -> Self {
-        Self::from_static_ref(name)
+        Self(name.into())
     }
 }
 
 impl From<&'_ &'static str> for Id {
     fn from(name: &'_ &'static str) -> Self {
-        Self::from_static_ref(*name)
+        Self(name.into())
     }
 }
 
@@ -102,64 +100,23 @@ impl PartialEq<str> for Id {
     }
 }
 
-impl<'s> PartialEq<&'s str> for Id {
+impl PartialEq<&'_ str> for Id {
     #[inline]
     fn eq(&self, other: &&str) -> bool {
         PartialEq::eq(self.as_str(), *other)
     }
 }
 
-impl PartialEq<String> for Id {
+impl PartialEq<Str> for Id {
     #[inline]
-    fn eq(&self, other: &String) -> bool {
+    fn eq(&self, other: &Str) -> bool {
         PartialEq::eq(self.as_str(), other.as_str())
     }
 }
 
-#[derive(Clone)]
-enum Inner {
-    Static(&'static str),
-    Owned(Box<str>),
-}
-
-impl Inner {
-    fn as_str(&self) -> &str {
-        match self {
-            Self::Static(s) => s,
-            Self::Owned(s) => s.as_ref(),
-        }
-    }
-}
-
-impl Default for Inner {
-    fn default() -> Self {
-        Self::Static("")
-    }
-}
-
-impl PartialEq for Inner {
-    fn eq(&self, other: &Inner) -> bool {
-        self.as_str() == other.as_str()
-    }
-}
-
-impl PartialOrd for Inner {
-    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
-        self.as_str().partial_cmp(other.as_str())
-    }
-}
-
-impl Ord for Inner {
-    fn cmp(&self, other: &Inner) -> std::cmp::Ordering {
-        self.as_str().cmp(other.as_str())
-    }
-}
-
-impl Eq for Inner {}
-
-impl std::hash::Hash for Inner {
+impl PartialEq<std::string::String> for Id {
     #[inline]
-    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
-        self.as_str().hash(state);
+    fn eq(&self, other: &std::string::String) -> bool {
+        PartialEq::eq(self.as_str(), other.as_str())
     }
 }
diff --git a/src/util/mod.rs b/src/util/mod.rs
index ed38fbc5..66fecae6 100644
--- a/src/util/mod.rs
+++ b/src/util/mod.rs
@@ -4,9 +4,13 @@ mod flat_map;
 mod flat_set;
 mod graph;
 mod id;
+mod os_str;
+mod str;
 mod str_to_bool;
 
 pub use self::id::Id;
+pub use self::os_str::OsStr;
+pub use self::str::Str;
 
 pub(crate) use self::flat_map::Entry;
 pub(crate) use self::flat_map::FlatMap;
diff --git a/src/util/os_str.rs b/src/util/os_str.rs
new file mode 100644
index 00000000..e9d7767a
--- /dev/null
+++ b/src/util/os_str.rs
@@ -0,0 +1,204 @@
+/// A UTF-8-encoded fixed string
+#[derive(Default, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
+pub struct OsStr {
+    name: Inner,
+}
+
+impl OsStr {
+    pub(crate) fn from_string(name: std::ffi::OsString) -> Self {
+        Self {
+            name: Inner::Owned(name.into_boxed_os_str()),
+        }
+    }
+
+    pub(crate) fn from_ref(name: &std::ffi::OsStr) -> Self {
+        Self {
+            name: Inner::Owned(Box::from(name)),
+        }
+    }
+
+    pub(crate) const fn from_static_ref(name: &'static std::ffi::OsStr) -> Self {
+        Self {
+            name: Inner::Static(name),
+        }
+    }
+
+    /// Get the raw string of the `OsStr`
+    pub fn as_os_str(&self) -> &std::ffi::OsStr {
+        self.name.as_os_str()
+    }
+}
+
+impl From<&'_ OsStr> for OsStr {
+    fn from(id: &'_ OsStr) -> Self {
+        id.clone()
+    }
+}
+
+impl From<std::ffi::OsString> for OsStr {
+    fn from(name: std::ffi::OsString) -> Self {
+        Self::from_string(name)
+    }
+}
+
+impl From<&'_ std::ffi::OsString> for OsStr {
+    fn from(name: &'_ std::ffi::OsString) -> Self {
+        Self::from_ref(name.as_os_str())
+    }
+}
+
+impl From<std::string::String> for OsStr {
+    fn from(name: std::string::String) -> Self {
+        Self::from_string(name.into())
+    }
+}
+
+impl From<&'_ std::string::String> for OsStr {
+    fn from(name: &'_ std::string::String) -> Self {
+        Self::from_ref(name.as_str().as_ref())
+    }
+}
+
+impl From<&'static std::ffi::OsStr> for OsStr {
+    fn from(name: &'static std::ffi::OsStr) -> Self {
+        Self::from_static_ref(name)
+    }
+}
+
+impl From<&'_ &'static std::ffi::OsStr> for OsStr {
+    fn from(name: &'_ &'static std::ffi::OsStr) -> Self {
+        Self::from_static_ref(*name)
+    }
+}
+
+impl From<&'static str> for OsStr {
+    fn from(name: &'static str) -> Self {
+        Self::from_static_ref(name.as_ref())
+    }
+}
+
+impl From<&'_ &'static str> for OsStr {
+    fn from(name: &'_ &'static str) -> Self {
+        Self::from_static_ref((*name).as_ref())
+    }
+}
+
+impl std::fmt::Debug for OsStr {
+    #[inline]
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        std::fmt::Debug::fmt(self.as_os_str(), f)
+    }
+}
+
+impl std::ops::Deref for OsStr {
+    type Target = std::ffi::OsStr;
+
+    #[inline]
+    fn deref(&self) -> &std::ffi::OsStr {
+        self.as_os_str()
+    }
+}
+
+impl AsRef<std::ffi::OsStr> for OsStr {
+    #[inline]
+    fn as_ref(&self) -> &std::ffi::OsStr {
+        self.as_os_str()
+    }
+}
+
+impl AsRef<std::path::Path> for OsStr {
+    #[inline]
+    fn as_ref(&self) -> &std::path::Path {
+        std::path::Path::new(self)
+    }
+}
+
+impl std::borrow::Borrow<std::ffi::OsStr> for OsStr {
+    #[inline]
+    fn borrow(&self) -> &std::ffi::OsStr {
+        self.as_os_str()
+    }
+}
+
+impl PartialEq<str> for OsStr {
+    #[inline]
+    fn eq(&self, other: &str) -> bool {
+        PartialEq::eq(self.as_os_str(), other)
+    }
+}
+
+impl PartialEq<&'_ str> for OsStr {
+    #[inline]
+    fn eq(&self, other: &&str) -> bool {
+        PartialEq::eq(self.as_os_str(), *other)
+    }
+}
+
+impl<'s> PartialEq<&'s std::ffi::OsStr> for OsStr {
+    #[inline]
+    fn eq(&self, other: &&std::ffi::OsStr) -> bool {
+        PartialEq::eq(self.as_os_str(), *other)
+    }
+}
+
+impl PartialEq<std::string::String> for OsStr {
+    #[inline]
+    fn eq(&self, other: &std::string::String) -> bool {
+        PartialEq::eq(self.as_os_str(), other.as_str())
+    }
+}
+
+impl PartialEq<std::ffi::OsString> for OsStr {
+    #[inline]
+    fn eq(&self, other: &std::ffi::OsString) -> bool {
+        PartialEq::eq(self.as_os_str(), other.as_os_str())
+    }
+}
+
+#[derive(Clone)]
+enum Inner {
+    Static(&'static std::ffi::OsStr),
+    Owned(Box<std::ffi::OsStr>),
+}
+
+impl Inner {
+    fn as_os_str(&self) -> &std::ffi::OsStr {
+        match self {
+            Self::Static(s) => s,
+            Self::Owned(s) => s.as_ref(),
+        }
+    }
+}
+
+impl Default for Inner {
+    fn default() -> Self {
+        Self::Static(std::ffi::OsStr::new(""))
+    }
+}
+
+impl PartialEq for Inner {
+    fn eq(&self, other: &Inner) -> bool {
+        self.as_os_str() == other.as_os_str()
+    }
+}
+
+impl PartialOrd for Inner {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        self.as_os_str().partial_cmp(other.as_os_str())
+    }
+}
+
+impl Ord for Inner {
+    fn cmp(&self, other: &Inner) -> std::cmp::Ordering {
+        self.as_os_str().cmp(other.as_os_str())
+    }
+}
+
+impl Eq for Inner {}
+
+impl std::hash::Hash for Inner {
+    #[inline]
+    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+        self.as_os_str().hash(state);
+    }
+}
diff --git a/src/util/str.rs b/src/util/str.rs
new file mode 100644
index 00000000..ef349a96
--- /dev/null
+++ b/src/util/str.rs
@@ -0,0 +1,187 @@
+/// A UTF-8-encoded fixed string
+#[derive(Default, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
+pub struct Str {
+    name: Inner,
+}
+
+impl Str {
+    pub(crate) fn from_string(name: std::string::String) -> Self {
+        Self {
+            name: Inner::Owned(name.into_boxed_str()),
+        }
+    }
+
+    pub(crate) fn from_ref(name: &str) -> Self {
+        Self {
+            name: Inner::Owned(Box::from(name)),
+        }
+    }
+
+    pub(crate) const fn from_static_ref(name: &'static str) -> Self {
+        Self {
+            name: Inner::Static(name),
+        }
+    }
+
+    /// Get the raw string of the `Str`
+    pub fn as_str(&self) -> &str {
+        self.name.as_str()
+    }
+}
+
+impl From<&'_ Str> for Str {
+    fn from(id: &'_ Str) -> Self {
+        id.clone()
+    }
+}
+
+impl From<std::string::String> for Str {
+    fn from(name: std::string::String) -> Self {
+        Self::from_string(name)
+    }
+}
+
+impl From<&'_ std::string::String> for Str {
+    fn from(name: &'_ std::string::String) -> Self {
+        Self::from_ref(name.as_str())
+    }
+}
+
+impl From<&'static str> for Str {
+    fn from(name: &'static str) -> Self {
+        Self::from_static_ref(name)
+    }
+}
+
+impl From<&'_ &'static str> for Str {
+    fn from(name: &'_ &'static str) -> Self {
+        Self::from_static_ref(*name)
+    }
+}
+
+impl std::fmt::Display for Str {
+    #[inline]
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        std::fmt::Display::fmt(self.as_str(), f)
+    }
+}
+
+impl std::fmt::Debug for Str {
+    #[inline]
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        std::fmt::Debug::fmt(self.as_str(), f)
+    }
+}
+
+impl std::ops::Deref for Str {
+    type Target = str;
+
+    #[inline]
+    fn deref(&self) -> &str {
+        self.as_str()
+    }
+}
+
+impl AsRef<str> for Str {
+    #[inline]
+    fn as_ref(&self) -> &str {
+        self.as_str()
+    }
+}
+
+impl AsRef<[u8]> for Str {
+    #[inline]
+    fn as_ref(&self) -> &[u8] {
+        self.as_bytes()
+    }
+}
+
+impl AsRef<std::ffi::OsStr> for Str {
+    #[inline]
+    fn as_ref(&self) -> &std::ffi::OsStr {
+        (&**self).as_ref()
+    }
+}
+
+impl AsRef<std::path::Path> for Str {
+    #[inline]
+    fn as_ref(&self) -> &std::path::Path {
+        std::path::Path::new(self)
+    }
+}
+
+impl std::borrow::Borrow<str> for Str {
+    #[inline]
+    fn borrow(&self) -> &str {
+        self.as_str()
+    }
+}
+
+impl PartialEq<str> for Str {
+    #[inline]
+    fn eq(&self, other: &str) -> bool {
+        PartialEq::eq(self.as_str(), other)
+    }
+}
+
+impl PartialEq<&'_ str> for Str {
+    #[inline]
+    fn eq(&self, other: &&str) -> bool {
+        PartialEq::eq(self.as_str(), *other)
+    }
+}
+
+impl PartialEq<std::string::String> for Str {
+    #[inline]
+    fn eq(&self, other: &std::string::String) -> bool {
+        PartialEq::eq(self.as_str(), other.as_str())
+    }
+}
+
+#[derive(Clone)]
+enum Inner {
+    Static(&'static str),
+    Owned(Box<str>),
+}
+
+impl Inner {
+    fn as_str(&self) -> &str {
+        match self {
+            Self::Static(s) => s,
+            Self::Owned(s) => s.as_ref(),
+        }
+    }
+}
+
+impl Default for Inner {
+    fn default() -> Self {
+        Self::Static("")
+    }
+}
+
+impl PartialEq for Inner {
+    fn eq(&self, other: &Inner) -> bool {
+        self.as_str() == other.as_str()
+    }
+}
+
+impl PartialOrd for Inner {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        self.as_str().partial_cmp(other.as_str())
+    }
+}
+
+impl Ord for Inner {
+    fn cmp(&self, other: &Inner) -> std::cmp::Ordering {
+        self.as_str().cmp(other.as_str())
+    }
+}
+
+impl Eq for Inner {}
+
+impl std::hash::Hash for Inner {
+    #[inline]
+    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+        self.as_str().hash(state);
+    }
+}


Test patch:
diff --git a/tests/builder/action.rs b/tests/builder/action.rs
index 9e7326a6..6f4bcda2 100644
--- a/tests/builder/action.rs
+++ b/tests/builder/action.rs
@@ -1,5 +1,6 @@
 #![allow(clippy::bool_assert_comparison)]
 
+use clap::builder::ArgPredicate;
 use clap::Arg;
 use clap::ArgAction;
 use clap::Command;
@@ -126,7 +127,7 @@ fn set_true_with_default_value_if_present() {
             Arg::new("mammal")
                 .long("mammal")
                 .action(ArgAction::SetTrue)
-                .default_value_if("dog", None, Some("true")),
+                .default_value_if("dog", ArgPredicate::IsPresent, Some("true")),
         )
         .arg(Arg::new("dog").long("dog").action(ArgAction::SetTrue));
 
@@ -153,7 +154,7 @@ fn set_true_with_default_value_if_value() {
             Arg::new("mammal")
                 .long("mammal")
                 .action(ArgAction::SetTrue)
-                .default_value_if("dog", Some("true"), Some("true")),
+                .default_value_if("dog", "true", Some("true")),
         )
         .arg(Arg::new("dog").long("dog").action(ArgAction::SetTrue));
 
@@ -263,7 +264,7 @@ fn set_false_with_default_value_if_present() {
             Arg::new("mammal")
                 .long("mammal")
                 .action(ArgAction::SetFalse)
-                .default_value_if("dog", None, Some("false")),
+                .default_value_if("dog", ArgPredicate::IsPresent, Some("false")),
         )
         .arg(Arg::new("dog").long("dog").action(ArgAction::SetFalse));
 
@@ -290,7 +291,7 @@ fn set_false_with_default_value_if_value() {
             Arg::new("mammal")
                 .long("mammal")
                 .action(ArgAction::SetFalse)
-                .default_value_if("dog", Some("false"), Some("false")),
+                .default_value_if("dog", "false", Some("false")),
         )
         .arg(Arg::new("dog").long("dog").action(ArgAction::SetFalse));
 
@@ -366,7 +367,7 @@ fn count_with_default_value_if_present() {
             Arg::new("mammal")
                 .long("mammal")
                 .action(ArgAction::Count)
-                .default_value_if("dog", None, Some("10")),
+                .default_value_if("dog", ArgPredicate::IsPresent, Some("10")),
         )
         .arg(Arg::new("dog").long("dog").action(ArgAction::Count));
 
@@ -393,7 +394,7 @@ fn count_with_default_value_if_value() {
             Arg::new("mammal")
                 .long("mammal")
                 .action(ArgAction::Count)
-                .default_value_if("dog", Some("2"), Some("10")),
+                .default_value_if("dog", "2", Some("10")),
         )
         .arg(Arg::new("dog").long("dog").action(ArgAction::Count));
 
diff --git a/tests/builder/default_vals.rs b/tests/builder/default_vals.rs
index 03f52140..fc01816a 100644
--- a/tests/builder/default_vals.rs
+++ b/tests/builder/default_vals.rs
@@ -2,6 +2,7 @@ use std::ffi::OsStr;
 use std::ffi::OsString;
 
 use super::utils;
+use clap::builder::ArgPredicate;
 use clap::{arg, error::ErrorKind, value_parser, Arg, ArgAction, Command};
 
 #[test]
@@ -184,7 +185,11 @@ fn osstr_positional_user_override() {
 fn default_if_arg_present_no_default() {
     let r = Command::new("df")
         .arg(arg!(--opt <FILE> "some arg").required(true))
-        .arg(arg!([arg] "some arg").default_value_if("opt", None, Some("default")))
+        .arg(arg!([arg] "some arg").default_value_if(
+            "opt",
+            ArgPredicate::IsPresent,
+            Some("default"),
+        ))
         .try_get_matches_from(vec!["", "--opt", "some"]);
     assert!(r.is_ok(), "{}", r.unwrap_err());
     let m = r.unwrap();
@@ -199,7 +204,11 @@ fn default_if_arg_present_no_default() {
 fn default_if_arg_present_no_default_user_override() {
     let r = Command::new("df")
         .arg(arg!(--opt <FILE> "some arg").required(false))
-        .arg(arg!([arg] "some arg").default_value_if("opt", None, Some("default")))
+        .arg(arg!([arg] "some arg").default_value_if(
+            "opt",
+            ArgPredicate::IsPresent,
+            Some("default"),
+        ))
         .try_get_matches_from(vec!["", "--opt", "some", "other"]);
     assert!(r.is_ok(), "{}", r.unwrap_err());
     let m = r.unwrap();
@@ -217,7 +226,7 @@ fn default_if_arg_present_no_arg_with_default() {
         .arg(
             arg!([arg] "some arg")
                 .default_value("first")
-                .default_value_if("opt", None, Some("default")),
+                .default_value_if("opt", ArgPredicate::IsPresent, Some("default")),
         )
         .try_get_matches_from(vec![""]);
     assert!(r.is_ok(), "{}", r.unwrap_err());
@@ -236,7 +245,7 @@ fn default_if_arg_present_with_default() {
         .arg(
             arg!([arg] "some arg")
                 .default_value("first")
-                .default_value_if("opt", None, Some("default")),
+                .default_value_if("opt", ArgPredicate::IsPresent, Some("default")),
         )
         .try_get_matches_from(vec!["", "--opt", "some"]);
     assert!(r.is_ok(), "{}", r.unwrap_err());
@@ -255,7 +264,7 @@ fn default_if_arg_present_with_default_user_override() {
         .arg(
             arg!([arg] "some arg")
                 .default_value("first")
-                .default_value_if("opt", None, Some("default")),
+                .default_value_if("opt", ArgPredicate::IsPresent, Some("default")),
         )
         .try_get_matches_from(vec!["", "--opt", "some", "other"]);
     assert!(r.is_ok(), "{}", r.unwrap_err());
@@ -274,7 +283,7 @@ fn default_if_arg_present_no_arg_with_default_user_override() {
         .arg(
             arg!([arg] "some arg")
                 .default_value("first")
-                .default_value_if("opt", None, Some("default")),
+                .default_value_if("opt", ArgPredicate::IsPresent, Some("default")),
         )
         .try_get_matches_from(vec!["", "other"]);
     assert!(r.is_ok(), "{}", r.unwrap_err());
@@ -292,7 +301,7 @@ fn default_if_arg_present_no_arg_with_default_user_override() {
 fn default_if_arg_present_with_value_no_default() {
     let r = Command::new("df")
         .arg(arg!(--opt <FILE> "some arg").required(false))
-        .arg(arg!([arg] "some arg").default_value_if("opt", Some("value"), Some("default")))
+        .arg(arg!([arg] "some arg").default_value_if("opt", "value", Some("default")))
         .try_get_matches_from(vec!["", "--opt", "value"]);
     assert!(r.is_ok(), "{}", r.unwrap_err());
     let m = r.unwrap();
@@ -307,7 +316,7 @@ fn default_if_arg_present_with_value_no_default() {
 fn default_if_arg_present_with_value_no_default_fail() {
     let r = Command::new("df")
         .arg(arg!(--opt <FILE> "some arg").required(false))
-        .arg(arg!([arg] "some arg").default_value_if("opt", Some("value"), Some("default")))
+        .arg(arg!([arg] "some arg").default_value_if("opt", "value", Some("default")))
         .try_get_matches_from(vec!["", "--opt", "other"]);
     assert!(r.is_ok(), "{}", r.unwrap_err());
     let m = r.unwrap();
@@ -319,7 +328,7 @@ fn default_if_arg_present_with_value_no_default_fail() {
 fn default_if_arg_present_with_value_no_default_user_override() {
     let r = Command::new("df")
         .arg(arg!(--opt <FILE> "some arg").required(false))
-        .arg(arg!([arg] "some arg").default_value_if("opt", Some("some"), Some("default")))
+        .arg(arg!([arg] "some arg").default_value_if("opt", "some", Some("default")))
         .try_get_matches_from(vec!["", "--opt", "some", "other"]);
     assert!(r.is_ok(), "{}", r.unwrap_err());
     let m = r.unwrap();
@@ -337,7 +346,7 @@ fn default_if_arg_present_with_value_no_arg_with_default() {
         .arg(
             arg!([arg] "some arg")
                 .default_value("first")
-                .default_value_if("opt", Some("some"), Some("default")),
+                .default_value_if("opt", "some", Some("default")),
         )
         .try_get_matches_from(vec![""]);
     assert!(r.is_ok(), "{}", r.unwrap_err());
@@ -356,7 +365,7 @@ fn default_if_arg_present_with_value_no_arg_with_default_fail() {
         .arg(
             arg!([arg] "some arg")
                 .default_value("first")
-                .default_value_if("opt", Some("some"), Some("default")),
+                .default_value_if("opt", "some", Some("default")),
         )
         .try_get_matches_from(vec!["", "--opt", "other"]);
     assert!(r.is_ok(), "{}", r.unwrap_err());
@@ -375,7 +384,7 @@ fn default_if_arg_present_with_value_with_default() {
         .arg(
             arg!([arg] "some arg")
                 .default_value("first")
-                .default_value_if("opt", Some("some"), Some("default")),
+                .default_value_if("opt", "some", Some("default")),
         )
         .try_get_matches_from(vec!["", "--opt", "some"]);
     assert!(r.is_ok(), "{}", r.unwrap_err());
@@ -394,7 +403,7 @@ fn default_if_arg_present_with_value_with_default_user_override() {
         .arg(
             arg!([arg] "some arg")
                 .default_value("first")
-                .default_value_if("opt", Some("some"), Some("default")),
+                .default_value_if("opt", "some", Some("default")),
         )
         .try_get_matches_from(vec!["", "--opt", "some", "other"]);
     assert!(r.is_ok(), "{}", r.unwrap_err());
@@ -413,7 +422,7 @@ fn default_if_arg_present_no_arg_with_value_with_default_user_override() {
         .arg(
             arg!([arg] "some arg")
                 .default_value("first")
-                .default_value_if("opt", Some("some"), Some("default")),
+                .default_value_if("opt", "some", Some("default")),
         )
         .try_get_matches_from(vec!["", "other"]);
     assert!(r.is_ok(), "{}", r.unwrap_err());
@@ -432,7 +441,7 @@ fn default_if_arg_present_no_arg_with_value_with_default_user_override_fail() {
         .arg(
             arg!([arg] "some arg")
                 .default_value("first")
-                .default_value_if("opt", Some("some"), Some("default")),
+                .default_value_if("opt", "some", Some("default")),
         )
         .try_get_matches_from(vec!["", "--opt", "value", "other"]);
     assert!(r.is_ok(), "{}", r.unwrap_err());
@@ -450,7 +459,7 @@ fn default_if_arg_present_no_arg_with_value_with_default_user_override_fail() {
 fn no_default_if_arg_present_with_value_no_default() {
     let r = Command::new("df")
         .arg(arg!(--opt <FILE> "some arg").required(false))
-        .arg(arg!([arg] "some arg").default_value_if("opt", Some("value"), None))
+        .arg(arg!([arg] "some arg").default_value_if("opt", "value", None))
         .try_get_matches_from(vec!["", "--opt", "value"]);
     assert!(r.is_ok(), "{}", r.unwrap_err());
     let m = r.unwrap();
@@ -464,7 +473,7 @@ fn no_default_if_arg_present_with_value_with_default() {
         .arg(
             arg!([arg] "some arg")
                 .default_value("default")
-                .default_value_if("opt", Some("value"), None),
+                .default_value_if("opt", "value", None),
         )
         .try_get_matches_from(vec!["", "--opt", "value"]);
     assert!(r.is_ok(), "{}", r.unwrap_err());
@@ -480,7 +489,7 @@ fn no_default_if_arg_present_with_value_with_default_user_override() {
         .arg(
             arg!([arg] "some arg")
                 .default_value("default")
-                .default_value_if("opt", Some("value"), None),
+                .default_value_if("opt", "value", None),
         )
         .try_get_matches_from(vec!["", "--opt", "value", "other"]);
     assert!(r.is_ok(), "{}", r.unwrap_err());
@@ -499,7 +508,7 @@ fn no_default_if_arg_present_no_arg_with_value_with_default() {
         .arg(
             arg!([arg] "some arg")
                 .default_value("default")
-                .default_value_if("opt", Some("value"), None),
+                .default_value_if("opt", "value", None),
         )
         .try_get_matches_from(vec!["", "--opt", "other"]);
     assert!(r.is_ok(), "{}", r.unwrap_err());
@@ -522,8 +531,8 @@ fn default_ifs_arg_present() {
             arg!([arg] "some arg")
                 .default_value("first")
                 .default_value_ifs([
-                    ("opt", Some("some"), Some("default")),
-                    ("flag", None, Some("flg")),
+                    ("opt", ArgPredicate::from("some"), Some("default")),
+                    ("flag", ArgPredicate::IsPresent, Some("flg")),
                 ]),
         )
         .try_get_matches_from(vec!["", "--flag"]);
@@ -544,7 +553,10 @@ fn no_default_ifs_arg_present() {
         .arg(
             arg!([arg] "some arg")
                 .default_value("first")
-                .default_value_ifs([("opt", Some("some"), Some("default")), ("flag", None, None)]),
+                .default_value_ifs([
+                    ("opt", ArgPredicate::from("some"), Some("default")),
+                    ("flag", ArgPredicate::IsPresent, None),
+                ]),
         )
         .try_get_matches_from(vec!["", "--flag"]);
     assert!(r.is_ok(), "{}", r.unwrap_err());
@@ -562,8 +574,8 @@ fn default_ifs_arg_present_user_override() {
             arg!([arg] "some arg")
                 .default_value("first")
                 .default_value_ifs([
-                    ("opt", Some("some"), Some("default")),
-                    ("flag", None, Some("flg")),
+                    ("opt", ArgPredicate::from("some"), Some("default")),
+                    ("flag", ArgPredicate::IsPresent, Some("flg")),
                 ]),
         )
         .try_get_matches_from(vec!["", "--flag", "value"]);
@@ -585,8 +597,8 @@ fn default_ifs_arg_present_order() {
             arg!([arg] "some arg")
                 .default_value("first")
                 .default_value_ifs([
-                    ("opt", Some("some"), Some("default")),
-                    ("flag", None, Some("flg")),
+                    ("opt", ArgPredicate::from("some"), Some("default")),
+                    ("flag", ArgPredicate::IsPresent, Some("flg")),
                 ]),
         )
         .try_get_matches_from(vec!["", "--opt=some", "--flag"]);
@@ -612,11 +624,7 @@ fn default_value_ifs_os() {
             Arg::new("other")
                 .long("other")
                 .value_parser(value_parser!(OsString))
-                .default_value_ifs_os([(
-                    "flag",
-                    Some(OsStr::new("标记2")),
-                    Some(OsStr::new("flag=标记2")),
-                )]),
+                .default_value_ifs_os([("flag", "标记2", Some(OsStr::new("flag=标记2")))]),
         );
     let result = cmd.try_get_matches_from(["my_cargo", "--flag", "标记2"]);
     assert!(result.is_ok(), "{}", result.unwrap_err());
diff --git a/tests/builder/require.rs b/tests/builder/require.rs
index 92980a5a..4a891a34 100644
--- a/tests/builder/require.rs
+++ b/tests/builder/require.rs
@@ -1,5 +1,6 @@
 use super::utils;
 
+use clap::builder::ArgPredicate;
 use clap::{arg, error::ErrorKind, Arg, ArgAction, ArgGroup, Command};
 
 static REQUIRE_EQUALS: &str = "error: The following required arguments were not provided:
@@ -1047,7 +1048,11 @@ fn issue_1158_app() -> Command<'static> {
             arg!([ID] "ID")
                 .required_unless_present("config")
                 .conflicts_with("config")
-                .requires_all(["x", "y", "z"]),
+                .requires_ifs([
+                    (ArgPredicate::IsPresent, "x"),
+                    (ArgPredicate::IsPresent, "y"),
+                    (ArgPredicate::IsPresent, "z"),
+                ]),
         )
         .arg(arg!(x: -x <X> "X").required(false))
         .arg(arg!(y: -y <Y> "Y").required(false))