Repository: time-rs/time (Rust)

Issue:
`OffsetDateTime::format` (and `::format_into` and `::parse`) should allow `?Sized` for the `Formattable` (and `Parsable`) bound
https://github.com/time-rs/time/blob/d700977ce9bc34013ee9841e122edbc807f410e4/src/offset_date_time.rs#L808

This **can't** be used like this:

```rust
const FORMAT: &[FormatItem<'_>] = time::macros::format_description!("...");

time.format(FORMAT)
```

... even though `[FormatItem<'_>]: Formattable` and thus `&[FormatItem<'_>]` is `&impl Formattable`. This is because the `impl Formattable` bound disallows `!Sized` types like `[FormatItem<'_>]`

It should be `pub fn format(self, format: &(impl Formattable + ?Sized)) -> Result<String, error::Format> {`, and the same for `format_into` and `parse`

Compare `foo` and `foo2` in   https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=72a25a6049efc8d16f374a460cfa0dce

---

Workaround: write `time.format(&FORMAT)` instead, which works through the blanket impl for `Deref`.


Discussion:
The change, as you saw, is trivial. I don't believe this will introduce any compatibility concerns as it only _permits_ unsized parameters rather than requiring them. The methods only delegate to the trait, but I'd like to be sure that I won't want to change something in the future that would require the parameters to be sized.
>but I'd like to be sure that I won't want to change something in the future that would require the parameters to be sized.

So do you need something from me? Or are you saying you don't want to merge the PR?
Right now neither. I just need to think about future possibilities on my end. The PR is clearly correct if I decide I want this.



Patch:
diff --git a/benchmarks/formatting.rs b/benchmarks/formatting.rs
index 5ea4d1d44..c2688c84b 100644
--- a/benchmarks/formatting.rs
+++ b/benchmarks/formatting.rs
@@ -151,7 +151,7 @@ setup_benchmark! {
         ben.iter(|| {
             datetime!(1970-01-01 0:00).format_into(
                 &mut io::sink(),
-                &fd!("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]"),
+                fd!("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]"),
             )
         });
     }
diff --git a/src/date.rs b/src/date.rs
index f3885d966..a730f2760 100644
--- a/src/date.rs
+++ b/src/date.rs
@@ -657,7 +657,7 @@ impl Date {
     pub fn format_into(
         self,
         output: &mut impl io::Write,
-        format: &impl Formattable,
+        format: &(impl Formattable + ?Sized),
     ) -> Result<usize, error::Format> {
         format.format_into(output, Some(self), None, None)
     }
@@ -670,7 +670,7 @@ impl Date {
     /// assert_eq!(date!(2020 - 01 - 02).format(&format)?, "2020-01-02");
     /// # Ok::<_, time::Error>(())
     /// ```
-    pub fn format(self, format: &impl Formattable) -> Result<String, error::Format> {
+    pub fn format(self, format: &(impl Formattable + ?Sized)) -> Result<String, error::Format> {
         format.format(Some(self), None, None)
     }
 }
@@ -687,7 +687,10 @@ impl Date {
     /// assert_eq!(Date::parse("2020-01-02", &format)?, date!(2020 - 01 - 02));
     /// # Ok::<_, time::Error>(())
     /// ```
-    pub fn parse(input: &str, description: &impl Parsable) -> Result<Self, error::Parse> {
+    pub fn parse(
+        input: &str,
+        description: &(impl Parsable + ?Sized),
+    ) -> Result<Self, error::Parse> {
         description.parse_date(input.as_bytes())
     }
 }
diff --git a/src/offset_date_time.rs b/src/offset_date_time.rs
index 4979371aa..c248d9286 100644
--- a/src/offset_date_time.rs
+++ b/src/offset_date_time.rs
@@ -779,7 +779,7 @@ impl OffsetDateTime {
     pub fn format_into(
         self,
         output: &mut impl io::Write,
-        format: &impl Formattable,
+        format: &(impl Formattable + ?Sized),
     ) -> Result<usize, error::Format> {
         let local = self.utc_datetime.utc_to_offset(self.offset);
         format.format_into(
@@ -805,7 +805,7 @@ impl OffsetDateTime {
     /// );
     /// # Ok::<_, time::Error>(())
     /// ```
-    pub fn format(self, format: &impl Formattable) -> Result<String, error::Format> {
+    pub fn format(self, format: &(impl Formattable + ?Sized)) -> Result<String, error::Format> {
         let local = self.utc_datetime.utc_to_offset(self.offset);
         format.format(Some(local.date), Some(local.time), Some(self.offset))
     }
@@ -829,7 +829,10 @@ impl OffsetDateTime {
     /// );
     /// # Ok::<_, time::Error>(())
     /// ```
-    pub fn parse(input: &str, description: &impl Parsable) -> Result<Self, error::Parse> {
+    pub fn parse(
+        input: &str,
+        description: &(impl Parsable + ?Sized),
+    ) -> Result<Self, error::Parse> {
         description.parse_offset_date_time(input.as_bytes())
     }
 }
diff --git a/src/primitive_date_time.rs b/src/primitive_date_time.rs
index 89456c7de..538a0388b 100644
--- a/src/primitive_date_time.rs
+++ b/src/primitive_date_time.rs
@@ -507,7 +507,7 @@ impl PrimitiveDateTime {
     pub fn format_into(
         self,
         output: &mut impl io::Write,
-        format: &impl Formattable,
+        format: &(impl Formattable + ?Sized),
     ) -> Result<usize, error::Format> {
         format.format_into(output, Some(self.date), Some(self.time), None)
     }
@@ -524,7 +524,7 @@ impl PrimitiveDateTime {
     /// );
     /// # Ok::<_, time::Error>(())
     /// ```
-    pub fn format(self, format: &impl Formattable) -> Result<String, error::Format> {
+    pub fn format(self, format: &(impl Formattable + ?Sized)) -> Result<String, error::Format> {
         format.format(Some(self.date), Some(self.time), None)
     }
 }
@@ -544,7 +544,10 @@ impl PrimitiveDateTime {
     /// );
     /// # Ok::<_, time::Error>(())
     /// ```
-    pub fn parse(input: &str, description: &impl Parsable) -> Result<Self, error::Parse> {
+    pub fn parse(
+        input: &str,
+        description: &(impl Parsable + ?Sized),
+    ) -> Result<Self, error::Parse> {
         description.parse_date_time(input.as_bytes())
     }
 }
diff --git a/src/time.rs b/src/time.rs
index 6912f3da8..e640c0520 100644
--- a/src/time.rs
+++ b/src/time.rs
@@ -415,7 +415,7 @@ impl Time {
     pub fn format_into(
         self,
         output: &mut impl io::Write,
-        format: &impl Formattable,
+        format: &(impl Formattable + ?Sized),
     ) -> Result<usize, crate::error::Format> {
         format.format_into(output, None, Some(self), None)
     }
@@ -428,7 +428,10 @@ impl Time {
     /// assert_eq!(time!(12:00).format(&format)?, "12:00:00");
     /// # Ok::<_, time::Error>(())
     /// ```
-    pub fn format(self, format: &impl Formattable) -> Result<String, crate::error::Format> {
+    pub fn format(
+        self,
+        format: &(impl Formattable + ?Sized),
+    ) -> Result<String, crate::error::Format> {
         format.format(None, Some(self), None)
     }
 }
@@ -445,7 +448,10 @@ impl Time {
     /// assert_eq!(Time::parse("12:00:00", &format)?, time!(12:00));
     /// # Ok::<_, time::Error>(())
     /// ```
-    pub fn parse(input: &str, description: &impl Parsable) -> Result<Self, error::Parse> {
+    pub fn parse(
+        input: &str,
+        description: &(impl Parsable + ?Sized),
+    ) -> Result<Self, error::Parse> {
         description.parse_time(input.as_bytes())
     }
 }
diff --git a/src/utc_offset.rs b/src/utc_offset.rs
index 0dfc00c29..4f9704fb6 100644
--- a/src/utc_offset.rs
+++ b/src/utc_offset.rs
@@ -260,7 +260,7 @@ impl UtcOffset {
     pub fn format_into(
         self,
         output: &mut impl io::Write,
-        format: &impl Formattable,
+        format: &(impl Formattable + ?Sized),
     ) -> Result<usize, error::Format> {
         format.format_into(output, None, None, Some(self))
     }
@@ -273,7 +273,7 @@ impl UtcOffset {
     /// assert_eq!(offset!(+1).format(&format)?, "+01:00");
     /// # Ok::<_, time::Error>(())
     /// ```
-    pub fn format(self, format: &impl Formattable) -> Result<String, error::Format> {
+    pub fn format(self, format: &(impl Formattable + ?Sized)) -> Result<String, error::Format> {
         format.format(None, None, Some(self))
     }
 }
@@ -290,7 +290,10 @@ impl UtcOffset {
     /// assert_eq!(UtcOffset::parse("-03:42", &format)?, offset!(-3:42));
     /// # Ok::<_, time::Error>(())
     /// ```
-    pub fn parse(input: &str, description: &impl Parsable) -> Result<Self, error::Parse> {
+    pub fn parse(
+        input: &str,
+        description: &(impl Parsable + ?Sized),
+    ) -> Result<Self, error::Parse> {
         description.parse_offset(input.as_bytes())
     }
 }


Test patch:
diff --git a/tests/integration/error.rs b/tests/integration/error.rs
index 2132c956f..064dedc7e 100644
--- a/tests/integration/error.rs
+++ b/tests/integration/error.rs
@@ -42,7 +42,7 @@ fn insufficient_type_information() -> Format {
 }
 
 fn unexpected_trailing_characters() -> Parse {
-    Time::parse("a", &format_description!("")).unwrap_err()
+    Time::parse("a", format_description!("")).unwrap_err()
 }
 
 fn invalid_format_description() -> InvalidFormatDescription {
diff --git a/tests/integration/formatting.rs b/tests/integration/formatting.rs
index 9b047fb81..68821dde7 100644
--- a/tests/integration/formatting.rs
+++ b/tests/integration/formatting.rs
@@ -105,55 +105,55 @@ fn format_time() -> time::Result<()> {
 
     for &(format_description, output) in &format_output {
         assert_eq!(
-            time!(13:02:03.456_789_012).format(&format_description)?,
+            time!(13:02:03.456_789_012).format(format_description)?,
             output
         );
         assert!(
             time!(13:02:03.456_789_012)
-                .format_into(&mut io::sink(), &format_description)
+                .format_into(&mut io::sink(), format_description)
                 .is_ok()
         );
     }
 
     assert_eq!(
-        time!(1:02:03).format(&fd!("[hour repr:12][period]"))?,
+        time!(1:02:03).format(fd!("[hour repr:12][period]"))?,
         "01AM"
     );
     assert_eq!(
-        Time::MIDNIGHT.format(&fd!("[hour repr:12][period case:lower]"))?,
+        Time::MIDNIGHT.format(fd!("[hour repr:12][period case:lower]"))?,
         "12am"
     );
-    assert_eq!(Time::MIDNIGHT.format(&fd!("[subsecond digits:1+]"))?, "0");
+    assert_eq!(Time::MIDNIGHT.format(fd!("[subsecond digits:1+]"))?, "0");
     assert_eq!(
-        time!(0:00:00.01).format(&fd!("[subsecond digits:1+]"))?,
+        time!(0:00:00.01).format(fd!("[subsecond digits:1+]"))?,
         "01"
     );
     assert_eq!(
-        time!(0:00:00.001).format(&fd!("[subsecond digits:1+]"))?,
+        time!(0:00:00.001).format(fd!("[subsecond digits:1+]"))?,
         "001"
     );
     assert_eq!(
-        time!(0:00:00.0001).format(&fd!("[subsecond digits:1+]"))?,
+        time!(0:00:00.0001).format(fd!("[subsecond digits:1+]"))?,
         "0001"
     );
     assert_eq!(
-        time!(0:00:00.00001).format(&fd!("[subsecond digits:1+]"))?,
+        time!(0:00:00.00001).format(fd!("[subsecond digits:1+]"))?,
         "00001"
     );
     assert_eq!(
-        time!(0:00:00.000001).format(&fd!("[subsecond digits:1+]"))?,
+        time!(0:00:00.000001).format(fd!("[subsecond digits:1+]"))?,
         "000001"
     );
     assert_eq!(
-        time!(0:00:00.0000001).format(&fd!("[subsecond digits:1+]"))?,
+        time!(0:00:00.0000001).format(fd!("[subsecond digits:1+]"))?,
         "0000001"
     );
     assert_eq!(
-        time!(0:00:00.00000001).format(&fd!("[subsecond digits:1+]"))?,
+        time!(0:00:00.00000001).format(fd!("[subsecond digits:1+]"))?,
         "00000001"
     );
     assert_eq!(
-        time!(0:00:00.000000001).format(&fd!("[subsecond digits:1+]"))?,
+        time!(0:00:00.000000001).format(fd!("[subsecond digits:1+]"))?,
         "000000001"
     );
 
@@ -205,10 +205,10 @@ fn format_date() -> time::Result<()> {
     ];
 
     for &(format_description, output) in &format_output {
-        assert_eq!(date!(2019 - 12 - 31).format(&format_description)?, output);
+        assert_eq!(date!(2019 - 12 - 31).format(format_description)?, output);
         assert!(
             date!(2019 - 12 - 31)
-                .format_into(&mut io::sink(), &format_description)
+                .format_into(&mut io::sink(), format_description)
                 .is_ok()
         );
     }
@@ -257,10 +257,10 @@ fn format_offset() -> time::Result<()> {
     ];
 
     for &(value, format_description, output) in &value_format_output {
-        assert_eq!(value.format(&format_description)?, output);
+        assert_eq!(value.format(format_description)?, output);
         assert!(
             value
-                .format_into(&mut io::sink(), &format_description)
+                .format_into(&mut io::sink(), format_description)
                 .is_ok()
         );
     }
@@ -286,12 +286,12 @@ fn format_pdt() -> time::Result<()> {
     let format_description = fd!("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]");
 
     assert_eq!(
-        datetime!(1970-01-01 0:00).format(&format_description)?,
+        datetime!(1970-01-01 0:00).format(format_description)?,
         "1970-01-01 00:00:00.0"
     );
     assert!(
         datetime!(1970-01-01 0:00)
-            .format_into(&mut io::sink(), &format_description)
+            .format_into(&mut io::sink(), format_description)
             .is_ok()
     );
 
@@ -346,7 +346,7 @@ fn insufficient_type_information() {
             Err(time::error::Format::InsufficientTypeInformation { .. })
         ));
     };
-    assert_insufficient_type_information(Time::MIDNIGHT.format(&fd!("[year]")));
+    assert_insufficient_type_information(Time::MIDNIGHT.format(fd!("[year]")));
     assert_insufficient_type_information(Time::MIDNIGHT.format(&Rfc3339));
     assert_insufficient_type_information(date!(2021 - 001).format(&Rfc3339));
     assert_insufficient_type_information(datetime!(2021 - 001 0:00).format(&Rfc3339));
@@ -366,7 +366,7 @@ fn failed_write() -> time::Result<()> {
             matches!(res, Err(time::error::Format::StdIo(e)) if e.kind() == ErrorKind::WriteZero)
         );
     };
-    assert_err(Time::MIDNIGHT.format_into(bytes!(0), &fd!("foo")));
+    assert_err(Time::MIDNIGHT.format_into(bytes!(0), fd!("foo")));
     assert_err(Time::MIDNIGHT.format_into(bytes!(0), &FormatItem::Compound(fd!("foo"))));
     assert_err(OffsetDateTime::UNIX_EPOCH.format_into(bytes!(0), &Rfc3339));
     assert_err(OffsetDateTime::UNIX_EPOCH.format_into(bytes!(4), &Rfc3339));
@@ -386,15 +386,15 @@ fn failed_write() -> time::Result<()> {
     assert_err(datetime!(2021-001 0:00 +0:01).format_into(bytes!(20), &Rfc3339));
     assert_err(datetime!(2021-001 0:00 +0:01).format_into(bytes!(22), &Rfc3339));
     assert_err(datetime!(2021-001 0:00 +0:01).format_into(bytes!(23), &Rfc3339));
-    assert_err(Time::MIDNIGHT.format_into(bytes!(0), &fd!("[hour padding:space]")));
-    assert_err(Time::MIDNIGHT.format_into(bytes!(1), &fd!("[hour padding:space]")));
-    assert_err(offset!(+1).format_into(bytes!(0), &fd!("[offset_hour sign:mandatory]")));
-    assert_err(offset!(-1).format_into(bytes!(0), &fd!("[offset_hour]")));
-    assert_err(offset!(-1).format_into(bytes!(1), &fd!("[offset_hour]")));
-    assert_err(date!(-1 - 001).format_into(bytes!(0), &fd!("[year]")));
-    assert_err(date!(2021 - 001).format_into(bytes!(0), &fd!("[year sign:mandatory]")));
-    assert_err(date!(+999_999 - 001).format_into(bytes!(4), &fd!("[year]")));
-    assert_err(date!(+99_999 - 001).format_into(bytes!(4), &fd!("[year]")));
+    assert_err(Time::MIDNIGHT.format_into(bytes!(0), fd!("[hour padding:space]")));
+    assert_err(Time::MIDNIGHT.format_into(bytes!(1), fd!("[hour padding:space]")));
+    assert_err(offset!(+1).format_into(bytes!(0), fd!("[offset_hour sign:mandatory]")));
+    assert_err(offset!(-1).format_into(bytes!(0), fd!("[offset_hour]")));
+    assert_err(offset!(-1).format_into(bytes!(1), fd!("[offset_hour]")));
+    assert_err(date!(-1 - 001).format_into(bytes!(0), fd!("[year]")));
+    assert_err(date!(2021 - 001).format_into(bytes!(0), fd!("[year sign:mandatory]")));
+    assert_err(date!(+999_999 - 001).format_into(bytes!(4), fd!("[year]")));
+    assert_err(date!(+99_999 - 001).format_into(bytes!(4), fd!("[year]")));
 
     let component_names = [
         "day",