Repository: clap-rs/clap
Pull request #4223 (2022-09-16 21:49:00)

perf: Switch to &'static str by default 
Originally, clap carried a lifetime parameter.  When moving away from
that, we took the approach that dynamically generated strings are always
supported and `&'static str` was just an optimization.

The problem is the code size increase from this is dramatic.  So we're
taking the opposite approach and making dynamic formatting opt-in under
the `string` feature flag.  When deciding on an implementation, I
favored the faster one rather than the one with smaller code size since
small code size can be gotten through other means.

Before: 567.2 KiB, 15.975 µs
After: 541.1 KiB, 9.7855 µs
With `string`: 576.6 KiB, 13.016 µs


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 52fe3fb8..1015d013 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -118,6 +118,7 @@ dependencies = [
  "clap_derive",
  "clap_lex",
  "humantime",
+ "once_cell",
  "rustversion",
  "shlex",
  "snapbox",
diff --git a/Cargo.toml b/Cargo.toml
index d38c7c64..b18b4ca1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -60,7 +60,7 @@ default = [
   "suggestions",
 ]
 debug = ["clap_derive/debug", "dep:backtrace"] # Enables debug messages
-unstable-doc = ["derive", "cargo", "wrap_help", "env", "unicode", "perf", "unstable-replace", "unstable-grouped"] # for docs.rs
+unstable-doc = ["derive", "cargo", "wrap_help", "env", "unicode", "string", "unstable-replace", "unstable-grouped"] # for docs.rs
 
 # Used in default
 std = [] # support for no_std in a backwards-compatible way
@@ -69,12 +69,12 @@ suggestions = ["dep:strsim"]
 
 # Optional
 deprecated = ["clap_derive?/deprecated"] # Guided experience to prepare for next breaking release (at different stages of development, this may become default)
-derive = ["clap_derive"]
-cargo = [] # Disable if you're not using Cargo, enables Cargo-env-var-dependent macros
+derive = ["clap_derive", "dep:once_cell"]
+cargo = ["dep:once_cell"] # Disable if you're not using Cargo, enables Cargo-env-var-dependent macros
 wrap_help = ["dep:terminal_size"]
 env = [] # Use environment variables during arg parsing
 unicode = ["dep:unicode-width", "dep:unicase"]  # Support for unicode characters in arguments and help messages
-perf = [] # Optimize for runtime performance
+string = []  # Allow runtime generated strings
 
 # In-work features
 unstable-replace = []
@@ -95,6 +95,7 @@ termcolor = { version = "1.1.1", optional = true }
 terminal_size = { version = "0.2.1", optional = true }
 backtrace = { version = "0.3", optional = true }
 unicode-width = { version = "0.1.9", optional = true }
+once_cell = { version = "1.12.0", optional = true }
 
 [dev-dependencies]
 trybuild = "1.0.18"
diff --git a/Makefile b/Makefile
index 5a0ce9d7..48f91ce8 100644
--- a/Makefile
+++ b/Makefile
@@ -15,8 +15,8 @@ MSRV?=1.60.0
 _FEATURES = minimal default wasm full debug release
 _FEATURES_minimal = --no-default-features --features "std"
 _FEATURES_default =
-_FEATURES_wasm = --features "deprecated derive cargo env unicode perf unstable-replace unstable-grouped"
-_FEATURES_full = --features "deprecated derive cargo env unicode perf unstable-replace unstable-grouped wrap_help"
+_FEATURES_wasm = --features "deprecated derive cargo env unicode string unstable-replace unstable-grouped"
+_FEATURES_full = --features "deprecated derive cargo env unicode string unstable-replace unstable-grouped wrap_help"
 _FEATURES_next = ${_FEATURES_full} --features unstable-v5
 _FEATURES_debug = ${_FEATURES_full} --features debug --features clap_complete/debug
 _FEATURES_release = ${_FEATURES_full} --release
diff --git a/clap_complete/src/generator/mod.rs b/clap_complete/src/generator/mod.rs
index dd9c0584..6f34d345 100644
--- a/clap_complete/src/generator/mod.rs
+++ b/clap_complete/src/generator/mod.rs
@@ -170,7 +170,7 @@ pub fn generate_to<G, S, T>(
 ) -> Result<PathBuf, Error>
 where
     G: Generator,
-    S: Into<clap::builder::Str>,
+    S: Into<String>,
     T: Into<OsString>,
 {
     cmd.set_bin_name(bin_name);
@@ -223,7 +223,7 @@ where
 pub fn generate<G, S>(gen: G, cmd: &mut clap::Command, bin_name: S, buf: &mut dyn Write)
 where
     G: Generator,
-    S: Into<clap::builder::Str>,
+    S: Into<String>,
 {
     cmd.set_bin_name(bin_name);
     _generate::<G, S>(gen, cmd, buf)
@@ -232,7 +232,7 @@ where
 fn _generate<G, S>(gen: G, cmd: &mut clap::Command, buf: &mut dyn Write)
 where
     G: Generator,
-    S: Into<clap::builder::Str>,
+    S: Into<String>,
 {
     cmd.build();
 
diff --git a/clap_derive/src/item.rs b/clap_derive/src/item.rs
index 3808364b..627d11b5 100644
--- a/clap_derive/src/item.rs
+++ b/clap_derive/src/item.rs
@@ -535,17 +535,21 @@ impl Item {
                         .any(|a| a.magic == Some(MagicAttrName::ValueEnum))
                     {
                         quote_spanned!(attr.name.clone().span()=> {
-                            {
+                            static DEFAULT_VALUE: clap::__macro_refs::once_cell::sync::Lazy<String> = clap::__macro_refs::once_cell::sync::Lazy::new(|| {
                                 let val: #ty = #val;
                                 clap::ValueEnum::to_possible_value(&val).unwrap().get_name().to_owned()
-                            }
+                            });
+                            let s: &'static str = &*DEFAULT_VALUE;
+                            s
                         })
                     } else {
                         quote_spanned!(attr.name.clone().span()=> {
-                            {
+                            static DEFAULT_VALUE: clap::__macro_refs::once_cell::sync::Lazy<String> = clap::__macro_refs::once_cell::sync::Lazy::new(|| {
                                 let val: #ty = #val;
                                 ::std::string::ToString::to_string(&val)
-                            }
+                            });
+                            let s: &'static str = &*DEFAULT_VALUE;
+                            s
                         })
                     };
 
@@ -599,21 +603,34 @@ impl Item {
                                         })
                                 }
 
-                                iter_to_vals(#expr)
+                                static DEFAULT_STRINGS: clap::__macro_refs::once_cell::sync::Lazy<Vec<::std::string::String>> = clap::__macro_refs::once_cell::sync::Lazy::new(|| {
+                                    iter_to_vals(#expr).collect()
+                                });
+
+                                static DEFAULT_VALUES: clap::__macro_refs::once_cell::sync::Lazy<Vec<&str>> = clap::__macro_refs::once_cell::sync::Lazy::new(|| {
+                                    DEFAULT_STRINGS.iter().map(::std::string::String::as_str).collect()
+                                });
+                                DEFAULT_VALUES.iter().copied()
                             }
                         })
                     } else {
                         quote_spanned!(attr.name.clone().span()=> {
                             {
-                                fn iter_to_vals<T>(iterable: impl IntoIterator<Item = T>) -> Vec<String>
+                                fn iter_to_vals<T>(iterable: impl IntoIterator<Item = T>) -> impl Iterator<Item=String>
                                 where
                                     T: ::std::borrow::Borrow<#inner_type>
                                 {
-                                    iterable.into_iter().map(|val| val.borrow().to_string()).collect()
-
+                                    iterable.into_iter().map(|val| val.borrow().to_string())
                                 }
 
-                                iter_to_vals(#expr)
+                                static DEFAULT_STRINGS: clap::__macro_refs::once_cell::sync::Lazy<Vec<::std::string::String>> = clap::__macro_refs::once_cell::sync::Lazy::new(|| {
+                                    iter_to_vals(#expr).collect()
+                                });
+
+                                static DEFAULT_VALUES: clap::__macro_refs::once_cell::sync::Lazy<Vec<&str>> = clap::__macro_refs::once_cell::sync::Lazy::new(|| {
+                                    DEFAULT_STRINGS.iter().map(::std::string::String::as_str).collect()
+                                });
+                                DEFAULT_VALUES.iter().copied()
                             }
                         })
                     };
@@ -650,17 +667,21 @@ impl Item {
                         .any(|a| a.magic == Some(MagicAttrName::ValueEnum))
                     {
                         quote_spanned!(attr.name.clone().span()=> {
-                            {
+                            static DEFAULT_VALUE: clap::__macro_refs::once_cell::sync::Lazy<::std::ffi::OsString> = clap::__macro_refs::once_cell::sync::Lazy::new(|| {
                                 let val: #ty = #val;
                                 clap::ValueEnum::to_possible_value(&val).unwrap().get_name().to_owned()
-                            }
+                            });
+                            let s: &'static ::std::ffi::OsStr = &*DEFAULT_VALUE;
+                            s
                         })
                     } else {
                         quote_spanned!(attr.name.clone().span()=> {
-                            {
+                            static DEFAULT_VALUE: clap::__macro_refs::once_cell::sync::Lazy<::std::ffi::OsString> = clap::__macro_refs::once_cell::sync::Lazy::new(|| {
                                 let val: #ty = #val;
                                 ::std::ffi::OsString::from(val)
-                            }
+                            });
+                            let s: &'static ::std::ffi::OsStr = &*DEFAULT_VALUE;
+                            s
                         })
                     };
 
@@ -703,32 +724,45 @@ impl Item {
                     {
                         quote_spanned!(attr.name.clone().span()=> {
                             {
-                                fn iter_to_vals<T>(iterable: impl IntoIterator<Item = T>) -> impl Iterator<Item=String>
+                                fn iter_to_vals<T>(iterable: impl IntoIterator<Item = T>) -> impl Iterator<Item=::std::ffi::OsString>
                                 where
                                     T: ::std::borrow::Borrow<#inner_type>
                                 {
                                     iterable
                                         .into_iter()
                                         .map(|val| {
-                                            clap::ValueEnum::to_possible_value(val.borrow()).unwrap().get_name().to_owned()
+                                            clap::ValueEnum::to_possible_value(val.borrow()).unwrap().get_name().to_owned().into()
                                         })
                                 }
 
-                                iter_to_vals(#expr)
+                                static DEFAULT_OS_STRINGS: clap::__macro_refs::once_cell::sync::Lazy<Vec<::std::ffi::OsString>> = clap::__macro_refs::once_cell::sync::Lazy::new(|| {
+                                    iter_to_vals(#expr).collect()
+                                });
+
+                                static DEFAULT_VALUES: clap::__macro_refs::once_cell::sync::Lazy<Vec<&::std::ffi::OsStr>> = clap::__macro_refs::once_cell::sync::Lazy::new(|| {
+                                    DEFAULT_OS_STRINGS.iter().map(::std::ffi::OsString::as_os_str).collect()
+                                });
+                                DEFAULT_VALUES.iter().copied()
                             }
                         })
                     } else {
                         quote_spanned!(attr.name.clone().span()=> {
                             {
-                                fn iter_to_vals<T>(iterable: impl IntoIterator<Item = T>) -> Vec<::std::ffi::OsString>
+                                fn iter_to_vals<T>(iterable: impl IntoIterator<Item = T>) -> impl Iterator<Item=::std::ffi::OsString>
                                 where
                                     T: ::std::borrow::Borrow<#inner_type>
                                 {
-                                    iterable.into_iter().map(|val| val.borrow().into()).collect()
-
+                                    iterable.into_iter().map(|val| val.borrow().into())
                                 }
 
-                                iter_to_vals(#expr)
+                                static DEFAULT_OS_STRINGS: clap::__macro_refs::once_cell::sync::Lazy<Vec<::std::ffi::OsString>> = clap::__macro_refs::once_cell::sync::Lazy::new(|| {
+                                    iter_to_vals(#expr).collect()
+                                });
+
+                                static DEFAULT_VALUES: clap::__macro_refs::once_cell::sync::Lazy<Vec<&::std::ffi::OsStr>> = clap::__macro_refs::once_cell::sync::Lazy::new(|| {
+                                    DEFAULT_OS_STRINGS.iter().map(::std::ffi::OsString::as_os_str).collect()
+                                });
+                                DEFAULT_VALUES.iter().copied()
                             }
                         })
                     };
diff --git a/src/_features.rs b/src/_features.rs
index 40009ee6..7c12ef15 100644
--- a/src/_features.rs
+++ b/src/_features.rs
@@ -16,7 +16,7 @@
 //! * **env**: Turns on the usage of environment variables during parsing.
 //! * **unicode**: Turns on support for unicode characters (including emoji) in arguments and help messages.
 //! * **wrap_help**: Turns on the help text wrapping feature, based on the terminal size.
-//! * **perf**: Optimized for runtime performance.
+//! * **string**: Allow runtime generated strings
 //!
 //! #### Experimental features
 //!
diff --git a/src/builder/command.rs b/src/builder/command.rs
index c659e22c..a400f13e 100644
--- a/src/builder/command.rs
+++ b/src/builder/command.rs
@@ -70,8 +70,8 @@ pub struct Command {
     name: Str,
     long_flag: Option<Str>,
     short_flag: Option<char>,
-    display_name: Option<Str>,
-    bin_name: Option<Str>,
+    display_name: Option<String>,
+    bin_name: Option<String>,
     author: Option<Str>,
     version: Option<Str>,
     long_version: Option<Str>,
@@ -695,7 +695,7 @@ impl Command {
                 if let Some(f) = p.file_name() {
                     if let Some(s) = f.to_str() {
                         if self.bin_name.is_none() {
-                            self.bin_name = Some(Str::from(s.to_owned()));
+                            self.bin_name = Some(s.to_owned());
                         }
                     }
                 }
@@ -1376,7 +1376,7 @@ impl Command {
     /// # ;
     /// ```
     #[must_use]
-    pub fn bin_name(mut self, name: impl IntoResettable<Str>) -> Self {
+    pub fn bin_name(mut self, name: impl IntoResettable<String>) -> Self {
         self.bin_name = name.into_resettable().into_option();
         self
     }
@@ -1392,7 +1392,7 @@ impl Command {
     /// # ;
     /// ```
     #[must_use]
-    pub fn display_name(mut self, name: impl IntoResettable<Str>) -> Self {
+    pub fn display_name(mut self, name: impl IntoResettable<String>) -> Self {
         self.display_name = name.into_resettable().into_option();
         self
     }
@@ -3156,7 +3156,7 @@ impl Command {
     }
 
     /// Set binary name. Uses `&mut self` instead of `self`.
-    pub fn set_bin_name(&mut self, name: impl Into<Str>) {
+    pub fn set_bin_name(&mut self, name: impl Into<String>) {
         self.bin_name = Some(name.into());
     }
 
@@ -3167,6 +3167,7 @@ impl Command {
     }
 
     #[inline]
+    #[cfg(debug_assertions)]
     pub(crate) fn get_name_str(&self) -> &Str {
         &self.name
     }
@@ -3888,7 +3889,7 @@ impl Command {
             "Command::_build_subcommand Setting bin_name of {} to {:?}",
             sc.name, bin_name
         );
-        sc.bin_name = Some(Str::from(bin_name));
+        sc.bin_name = Some(bin_name);
 
         if sc.display_name.is_none() {
             let self_display_name = if is_multicall_set {
@@ -3910,7 +3911,7 @@ impl Command {
                 "Command::_build_subcommand Setting display_name of {} to {:?}",
                 sc.name, display_name
             );
-            sc.display_name = Some(Str::from(display_name));
+            sc.display_name = Some(display_name);
         }
 
         // Ensure all args are built and ready to parse
@@ -3989,7 +3990,7 @@ impl Command {
                         "Command::_build_bin_names:iter: Setting bin_name of {} to {:?}",
                         sc.name, bin_name
                     );
-                    sc.bin_name = Some(Str::from(bin_name));
+                    sc.bin_name = Some(bin_name);
                 } else {
                     debug!(
                         "Command::_build_bin_names::iter: Using existing bin_name of {} ({:?})",
@@ -4017,7 +4018,7 @@ impl Command {
                         "Command::_build_bin_names:iter: Setting display_name of {} to {:?}",
                         sc.name, display_name
                     );
-                    sc.display_name = Some(Str::from(display_name));
+                    sc.display_name = Some(display_name);
                 } else {
                     debug!(
                         "Command::_build_bin_names::iter: Using existing display_name of {} ({:?})",
diff --git a/src/builder/os_str.rs b/src/builder/os_str.rs
index 9e815d4d..f379b415 100644
--- a/src/builder/os_str.rs
+++ b/src/builder/os_str.rs
@@ -7,12 +7,14 @@ pub struct OsStr {
 }
 
 impl OsStr {
+    #[cfg(feature = "string")]
     pub(crate) fn from_string(name: std::ffi::OsString) -> Self {
         Self {
             name: Inner::from_string(name),
         }
     }
 
+    #[cfg(feature = "string")]
     pub(crate) fn from_ref(name: &std::ffi::OsStr) -> Self {
         Self {
             name: Inner::from_ref(name),
@@ -42,7 +44,7 @@ impl From<&'_ OsStr> for OsStr {
     }
 }
 
-#[cfg(feature = "perf")]
+#[cfg(feature = "string")]
 impl From<Str> for OsStr {
     fn from(id: Str) -> Self {
         match id.into_inner() {
@@ -52,10 +54,10 @@ impl From<Str> for OsStr {
     }
 }
 
-#[cfg(not(feature = "perf"))]
+#[cfg(not(feature = "string"))]
 impl From<Str> for OsStr {
     fn from(id: Str) -> Self {
-        Self::from_ref(std::ffi::OsStr::new(id.as_str()))
+        Self::from_static_ref(std::ffi::OsStr::new(id.into_inner().0))
     }
 }
 
@@ -69,31 +71,34 @@ impl From<&'_ Str> for OsStr {
     }
 }
 
-#[cfg(not(feature = "perf"))]
 impl From<&'_ Str> for OsStr {
     fn from(id: &'_ Str) -> Self {
-        Self::from_ref(std::ffi::OsStr::new(id.as_str()))
+        id.clone().into()
     }
 }
 
+#[cfg(feature = "string")]
 impl From<std::ffi::OsString> for OsStr {
     fn from(name: std::ffi::OsString) -> Self {
         Self::from_string(name)
     }
 }
 
+#[cfg(feature = "string")]
 impl From<&'_ std::ffi::OsString> for OsStr {
     fn from(name: &'_ std::ffi::OsString) -> Self {
         Self::from_ref(name.as_os_str())
     }
 }
 
+#[cfg(feature = "string")]
 impl From<std::string::String> for OsStr {
     fn from(name: std::string::String) -> Self {
         Self::from_string(name.into())
     }
 }
 
+#[cfg(feature = "string")]
 impl From<&'_ std::string::String> for OsStr {
     fn from(name: &'_ std::string::String) -> Self {
         Self::from_ref(name.as_str().as_ref())
@@ -238,7 +243,7 @@ impl PartialEq<OsStr> for std::ffi::OsString {
     }
 }
 
-#[cfg(feature = "perf")]
+#[cfg(feature = "string")]
 pub(crate) mod inner {
     #[derive(Clone)]
     pub(crate) enum Inner {
@@ -272,26 +277,18 @@ pub(crate) mod inner {
     }
 }
 
-#[cfg(not(feature = "perf"))]
+#[cfg(not(feature = "string"))]
 pub(crate) mod inner {
     #[derive(Clone)]
-    pub(crate) struct Inner(Box<std::ffi::OsStr>);
+    pub(crate) struct Inner(&'static std::ffi::OsStr);
 
     impl Inner {
-        pub(crate) fn from_string(name: std::ffi::OsString) -> Self {
-            Self(name.into_boxed_os_str())
-        }
-
-        pub(crate) fn from_ref(name: &std::ffi::OsStr) -> Self {
-            Self(Box::from(name))
-        }
-
         pub(crate) fn from_static_ref(name: &'static std::ffi::OsStr) -> Self {
-            Self::from_ref(name)
+            Self(name)
         }
 
         pub(crate) fn as_os_str(&self) -> &std::ffi::OsStr {
-            &self.0
+            self.0
         }
 
         pub(crate) fn into_os_string(self) -> std::ffi::OsString {
diff --git a/src/builder/resettable.rs b/src/builder/resettable.rs
index 9b20b602..0176d398 100644
--- a/src/builder/resettable.rs
+++ b/src/builder/resettable.rs
@@ -162,6 +162,12 @@ impl<I: Into<ValueParser>> IntoResettable<ValueParser> for I {
     }
 }
 
+impl<I: Into<String>> IntoResettable<String> for I {
+    fn into_resettable(self) -> Resettable<String> {
+        Resettable::Value(self.into())
+    }
+}
+
 impl<I: Into<StyledStr>> IntoResettable<StyledStr> for I {
     fn into_resettable(self) -> Resettable<StyledStr> {
         Resettable::Value(self.into())
diff --git a/src/builder/str.rs b/src/builder/str.rs
index 03c3ee3d..8dd9a7f4 100644
--- a/src/builder/str.rs
+++ b/src/builder/str.rs
@@ -5,12 +5,14 @@ pub struct Str {
 }
 
 impl Str {
+    #[cfg(feature = "string")]
     pub(crate) fn from_string(name: std::string::String) -> Self {
         Self {
             name: Inner::from_string(name),
         }
     }
 
+    #[cfg(feature = "string")]
     pub(crate) fn from_ref(name: &str) -> Self {
         Self {
             name: Inner::from_ref(name),
@@ -23,7 +25,6 @@ impl Str {
         }
     }
 
-    #[cfg(feature = "perf")]
     pub(crate) fn into_inner(self) -> Inner {
         self.name
     }
@@ -40,12 +41,14 @@ impl From<&'_ Str> for Str {
     }
 }
 
+#[cfg(feature = "string")]
 impl From<std::string::String> for Str {
     fn from(name: std::string::String) -> Self {
         Self::from_string(name)
     }
 }
 
+#[cfg(feature = "string")]
 impl From<&'_ std::string::String> for Str {
     fn from(name: &'_ std::string::String) -> Self {
         Self::from_ref(name.as_str())
@@ -211,7 +214,7 @@ impl PartialEq<Str> for std::string::String {
     }
 }
 
-#[cfg(feature = "perf")]
+#[cfg(feature = "string")]
 pub(crate) mod inner {
     #[derive(Clone)]
     pub(crate) enum Inner {
@@ -245,26 +248,18 @@ pub(crate) mod inner {
     }
 }
 
-#[cfg(not(feature = "perf"))]
+#[cfg(not(feature = "string"))]
 pub(crate) mod inner {
     #[derive(Clone)]
-    pub(crate) struct Inner(Box<str>);
+    pub(crate) struct Inner(pub(crate) &'static str);
 
     impl Inner {
-        pub(crate) fn from_string(name: std::string::String) -> Self {
-            Self(name.into_boxed_str())
-        }
-
-        pub(crate) fn from_ref(name: &str) -> Self {
-            Self(Box::from(name))
-        }
-
         pub(crate) fn from_static_ref(name: &'static str) -> Self {
-            Self::from_ref(name)
+            Self(name)
         }
 
         pub(crate) fn as_str(&self) -> &str {
-            &self.0
+            self.0
         }
 
         pub(crate) fn into_string(self) -> String {
diff --git a/src/lib.rs b/src/lib.rs
index 0f9d25a8..0059ca39 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -127,6 +127,13 @@ pub mod _features;
 #[cfg(feature = "unstable-doc")]
 pub mod _tutorial;
 
+#[doc(hidden)]
+pub mod __macro_refs {
+    #[cfg(any(feature = "derive", feature = "cargo"))]
+    #[doc(hidden)]
+    pub use once_cell;
+}
+
 #[macro_use]
 #[allow(missing_docs)]
 mod macros;
diff --git a/src/macros.rs b/src/macros.rs
index 3bc04647..c1754558 100644
--- a/src/macros.rs
+++ b/src/macros.rs
@@ -46,11 +46,14 @@ macro_rules! crate_version {
 #[macro_export]
 macro_rules! crate_authors {
     ($sep:expr) => {{
-        let authors = env!("CARGO_PKG_AUTHORS");
+        static authors: &str = env!("CARGO_PKG_AUTHORS");
         if authors.contains(':') {
-            $crate::builder::Str::from(authors.replace(':', $sep))
+            static CACHED: clap::__macro_refs::once_cell::sync::Lazy<String> =
+                clap::__macro_refs::once_cell::sync::Lazy::new(|| authors.replace(':', $sep));
+            let s: &'static str = &*CACHED;
+            s
         } else {
-            $crate::builder::Str::from(authors)
+            authors
         }
     }};
     () => {
diff --git a/src/parser/matches/arg_matches.rs b/src/parser/matches/arg_matches.rs
index cfbae26e..e743ca87 100644
--- a/src/parser/matches/arg_matches.rs
+++ b/src/parser/matches/arg_matches.rs
@@ -6,6 +6,7 @@ use std::iter::{Cloned, Flatten, Map};
 use std::slice::Iter;
 
 // Internal
+#[cfg(debug_assertions)]
 use crate::builder::Str;
 use crate::parser::AnyValue;
 use crate::parser::AnyValueId;
@@ -839,7 +840,7 @@ impl ArgMatches {
     /// }
     /// ```
     /// [subcommand]: crate::Command::subcommand
-    pub fn remove_subcommand(&mut self) -> Option<(Str, ArgMatches)> {
+    pub fn remove_subcommand(&mut self) -> Option<(String, ArgMatches)> {
         self.subcommand.take().map(|sc| (sc.name, sc.matches))
     }
 
@@ -1149,7 +1150,7 @@ impl ArgMatches {
 
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub(crate) struct SubCommand {
-    pub(crate) name: Str,
+    pub(crate) name: String,
     pub(crate) matches: ArgMatches,
 }
 
diff --git a/src/parser/parser.rs b/src/parser/parser.rs
index d736d0cf..1db8495e 100644
--- a/src/parser/parser.rs
+++ b/src/parser/parser.rs
@@ -9,7 +9,6 @@ use clap_lex::RawOsStr;
 use clap_lex::RawOsString;
 
 // Internal
-use crate::builder::Str;
 use crate::builder::{Arg, Command};
 use crate::error::Error as ClapError;
 use crate::error::Result as ClapResult;
@@ -418,7 +417,7 @@ impl<'cmd> Parser<'cmd> {
             {
                 // Get external subcommand name
                 let sc_name = match arg_os.to_value() {
-                    Ok(s) => Str::from(s.to_owned()),
+                    Ok(s) => s.to_owned(),
                     Err(_) => {
                         let _ = self.resolve_pending(matcher);
                         return Err(ClapError::invalid_utf8(
@@ -702,7 +701,7 @@ impl<'cmd> Parser<'cmd> {
                 }
             }
             matcher.subcommand(SubCommand {
-                name: sc.get_name_str().clone(),
+                name: sc.get_name().to_owned(),
                 matches: sc_matcher.into_inner(),
             });
         }
diff --git a/src/util/id.rs b/src/util/id.rs
index a6ee26ef..710d2ea7 100644
--- a/src/util/id.rs
+++ b/src/util/id.rs
@@ -45,12 +45,14 @@ impl From<&'_ Str> for Id {
     }
 }
 
+#[cfg(feature = "string")]
 impl From<std::string::String> for Id {
     fn from(name: std::string::String) -> Self {
         Self(name.into())
     }
 }
 
+#[cfg(feature = "string")]
 impl From<&'_ std::string::String> for Id {
     fn from(name: &'_ std::string::String) -> Self {
         Self(name.into())


Test patch:
diff --git a/tests/builder/cargo.rs b/tests/builder/cargo.rs
index 376bda74..21336cb0 100644
--- a/tests/builder/cargo.rs
+++ b/tests/builder/cargo.rs
@@ -70,6 +70,20 @@ fn crate_authors() {
     assert_eq!(err.to_string(), AUTHORS_ONLY);
 }
 
+#[test]
+fn crate_authors_with_sep() {
+    let res = Command::new("prog")
+        .version("1")
+        .author(crate_authors!(", "))
+        .help_template(utils::FULL_TEMPLATE)
+        .try_get_matches_from(vec!["prog", "--help"]);
+
+    assert!(res.is_err());
+    let err = res.unwrap_err();
+    assert_eq!(err.kind(), ErrorKind::DisplayHelp);
+    assert_eq!(err.to_string(), AUTHORS_ONLY);
+}
+
 #[test]
 fn crate_name() {
     let res = Command::new(crate_name!())
diff --git a/tests/examples.rs b/tests/examples.rs
index b3fb1d15..2dddf811 100644
--- a/tests/examples.rs
+++ b/tests/examples.rs
@@ -18,6 +18,8 @@ fn example_tests() {
         "suggestions",
         #[cfg(feature = "unicode")]
         "unicode",
+        #[cfg(feature = "string")]
+        "string",
         #[cfg(feature = "wrap_help")]
         "wrap_help",
         #[cfg(feature = "unstable-replace")]
diff --git a/tests/ui.rs b/tests/ui.rs
index 77bcac71..733e821e 100644
--- a/tests/ui.rs
+++ b/tests/ui.rs
@@ -18,6 +18,8 @@ fn ui_tests() {
         "suggestions",
         #[cfg(feature = "unicode")]
         "unicode",
+        #[cfg(feature = "string")]
+        "string",
         #[cfg(feature = "wrap_help")]
         "wrap_help",
         #[cfg(feature = "unstable-replace")]