rust-lang/rust · #165181

Implement `Duration::display_seconds`

frank-king · merged Nov 3, 20263 files · 341 + / 163 −
library/core/src/time.rs246 + / 163 −
@@ -75,6 +75,8 @@ const DAYS_PER_WEEK: u64 = 7; /// program output may appear in contexts that cannot rely on full Unicode /// compatibility, you may wish to format `Duration` objects yourself or use a /// crate to do so.+///+/// Alternatively, you can display the value as seconds via [`Duration::display_seconds`]. #[stable(feature = "duration", since = "1.3.0")] #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] #[rustc_diagnostic_item = "Duration"]@@ -1333,6 +1335,56 @@ impl Duration {     pub const fn div_duration_ceil(self, rhs: Duration) -> u128 {         self.as_nanos().div_ceil(rhs.as_nanos())     }++    /// Displays `Duration` in seconds, even if it only contains the nanosecond part.+    ///+    /// Unlike [`Duration::as_secs_f32`] or [`Duration::as_secs_f64`], which convert+    /// to floating-point numbers and may lose precision, this method displays a precise+    /// value. If the formatting precision `{:.N}` is explicitly specified:+    ///+    /// - `N` < 9: it rounds the subsecond part with tie resolution to even.+    /// - `N` = 9: it displays the precise 9-digit subsecond part.+    /// - `N` > 9: it appends trailing zeros to the subsecond part.+    ///+    /// # Examples+    /// ```+    /// #![feature(duration_display_seconds)]+    /// use std::time::Duration;+    ///+    /// // Display a duration longer than a second as seconds.+    /// assert_eq!(Duration::new(2, 0).display_seconds().to_string(), "2s");+    /// // Display a duration shorter than a second as seconds.+    /// assert_eq!(Duration::new(0, 2).display_seconds().to_string(), "0.000000002s");+    ///+    /// // `display_seconds` displays a precise value.+    /// assert_eq!(+    ///     format!("{}", Duration::new(1_000_000_000_000, 1).display_seconds()),+    ///     "1000000000000.000000001s",+    /// );+    /// // If formatting precision (< 9) is explicitly specified, it rounds+    /// // the subsecond part with tie resolution to even.+    /// assert_eq!(+    ///     format!("{:.0}", Duration::new(1_000_000_000_000, 1).display_seconds()),+    ///     "1000000000000s",+    /// );+    /// // If formatting precision (> 9) is explicitly specified, it appends+    /// // trailing zeros to the subsecond part.+    /// assert_eq!(+    ///     format!("{:.10}", Duration::new(1_000_000_000_000, 1).display_seconds()),+    ///     "1000000000000.0000000010s",+    /// );+    /// // In contrast, `as_secs_f64` converts it to a floating-point number+    /// // and may introduce floating-point computation errors.+    /// assert_eq!(+    ///     format!("{}", Duration::new(1_000_000_000_000, 1).as_secs_f64()),+    ///     "1000000000000",+    /// );+    /// ```+    #[unstable(feature = "duration_display_seconds", issue = "165108")]+    #[inline]+    pub const fn display_seconds(&self) -> DisplaySeconds {+        DisplaySeconds(*self)+    } }  #[stable(feature = "duration", since = "1.3.0")]@@ -1469,184 +1521,183 @@ impl<'a> Sum<&'a Duration> for Duration {     } } -#[stable(feature = "duration_debug_impl", since = "1.27.0")]-impl fmt::Debug for Duration {-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {-        /// Formats a floating point number in decimal notation.-        ///-        /// The number is given as the `integer_part` and a fractional part.-        /// The value of the fractional part is `fractional_part / divisor`. So-        /// `integer_part` = 3, `fractional_part` = 12 and `divisor` = 100-        /// represents the number `3.012`. Trailing zeros are omitted.-        ///-        /// `divisor` must not be above 100_000_000. It also should be a power-        /// of 10, everything else doesn't make sense. `fractional_part` has-        /// to be less than `10 * divisor`!-        ///-        /// A prefix and postfix may be added. The whole thing is padded-        /// to the formatter's `width`, if specified.-        fn fmt_decimal(-            f: &mut fmt::Formatter<'_>,-            integer_part: u64,-            mut fractional_part: u32,-            mut divisor: u32,-            prefix: &str,-            postfix: &str,-        ) -> fmt::Result {-            // Encode the fractional part into a temporary buffer. The buffer-            // only need to hold 9 elements, because `fractional_part` has to-            // be smaller than 10^9. The buffer is prefilled with '0' digits-            // to simplify the code below.-            let mut buf = [b'0'; 9];--            // The next digit is written at this position-            let mut pos = 0;--            // We keep writing digits into the buffer while there are non-zero-            // digits left and we haven't written enough digits yet.-            while fractional_part > 0 && pos < f.precision().unwrap_or(9) {-                // Write new digit into the buffer-                buf[pos] = b'0' + (fractional_part / divisor) as u8;--                fractional_part %= divisor;-                divisor /= 10;-                pos += 1;-            }+/// Formats a floating point number in decimal notation.+///+/// The number is given as the `integer_part` and a fractional part.+/// The value of the fractional part is `fractional_part / divisor`. So+/// `integer_part` = 3, `fractional_part` = 12 and `divisor` = 100+/// represents the number `3.012`. Trailing zeros are omitted.+///+/// `divisor` must not be above 100_000_000. It also should be a power+/// of 10, everything else doesn't make sense. `fractional_part` has+/// to be less than `10 * divisor`!+///+/// A prefix and postfix may be added. The whole thing is padded+/// to the formatter's `width`, if specified.+fn fmt_decimal(+    f: &mut fmt::Formatter<'_>,+    integer_part: u64,+    mut fractional_part: u32,+    mut divisor: u32,+    prefix: &str,+    postfix: &str,+) -> fmt::Result {+    // Encode the fractional part into a temporary buffer. The buffer+    // only need to hold 9 elements, because `fractional_part` has to+    // be smaller than 10^9. The buffer is prefilled with '0' digits+    // to simplify the code below.+    let mut buf = [b'0'; 9];++    // The next digit is written at this position+    let mut pos = 0;++    // We keep writing digits into the buffer while there are non-zero+    // digits left and we haven't written enough digits yet.+    while fractional_part > 0 && pos < f.precision().unwrap_or(9) {+        // Write new digit into the buffer+        buf[pos] = b'0' + (fractional_part / divisor) as u8;++        fractional_part %= divisor;+        divisor /= 10;+        pos += 1;+    } -            // If a precision < 9 was specified, there may be some non-zero-            // digits left that weren't written into the buffer. In that case we-            // need to perform rounding to match the semantics of printing-            // normal floating point numbers. However, we only need to do work-            // when rounding up. This happens if the first digit of the-            // remaining ones is >= 5. When the first digit is exactly 5, rounding-            // follows IEEE-754 round-ties-to-even semantics: we only round up-            // if the last written digit is odd.-            let integer_part = if fractional_part > 0 && fractional_part >= divisor * 5 {-                // For ties (fractional_part == divisor * 5), only round up if last digit is odd-                let is_tie = fractional_part == divisor * 5;-                let last_digit_is_odd = if pos > 0 {-                    (buf[pos - 1] - b'0') % 2 == 1-                } else {-                    // No fractional digits - check the integer part-                    (integer_part % 2) == 1-                };+    // If a precision < 9 was specified, there may be some non-zero+    // digits left that weren't written into the buffer. In that case we+    // need to perform rounding to match the semantics of printing+    // normal floating point numbers. However, we only need to do work+    // when rounding up. This happens if the first digit of the+    // remaining ones is >= 5. When the first digit is exactly 5, rounding+    // follows IEEE-754 round-ties-to-even semantics: we only round up+    // if the last written digit is odd.+    let integer_part = if fractional_part > 0 && fractional_part >= divisor * 5 {+        // For ties (fractional_part == divisor * 5), only round up if last digit is odd+        let is_tie = fractional_part == divisor * 5;+        let last_digit_is_odd = if pos > 0 {+            (buf[pos - 1] - b'0') % 2 == 1+        } else {+            // No fractional digits - check the integer part+            (integer_part % 2) == 1+        }; -                if is_tie && !last_digit_is_odd {-                    Some(integer_part)+        if is_tie && !last_digit_is_odd {+            Some(integer_part)+        } else {+            // Round up the number contained in the buffer. We go through+            // the buffer backwards and keep track of the carry.+            let mut rev_pos = pos;+            let mut carry = true;+            while carry && rev_pos > 0 {+                rev_pos -= 1;++                // If the digit in the buffer is not '9', we just need to+                // increment it and can stop then (since we don't have a+                // carry anymore). Otherwise, we set it to '0' (overflow)+                // and continue.+                if buf[rev_pos] < b'9' {+                    buf[rev_pos] += 1;+                    carry = false;                 } else {-                    // Round up the number contained in the buffer. We go through-                    // the buffer backwards and keep track of the carry.-                    let mut rev_pos = pos;-                    let mut carry = true;-                    while carry && rev_pos > 0 {-                        rev_pos -= 1;--                        // If the digit in the buffer is not '9', we just need to-                        // increment it and can stop then (since we don't have a-                        // carry anymore). Otherwise, we set it to '0' (overflow)-                        // and continue.-                        if buf[rev_pos] < b'9' {-                            buf[rev_pos] += 1;-                            carry = false;-                        } else {-                            buf[rev_pos] = b'0';-                        }-                    }--                    // If we still have the carry bit set, that means that we set-                    // the whole buffer to '0's and need to increment the integer-                    // part.-                    if carry {-                        // If `integer_part == u64::MAX` and precision < 9, any-                        // carry of the overflow during rounding of the-                        // `fractional_part` into the `integer_part` will cause the-                        // `integer_part` itself to overflow. Avoid this by using an-                        // `Option<u64>`, with `None` representing `u64::MAX + 1`.-                        integer_part.checked_add(1)-                    } else {-                        Some(integer_part)-                    }+                    buf[rev_pos] = b'0';                 }+            }++            // If we still have the carry bit set, that means that we set+            // the whole buffer to '0's and need to increment the integer+            // part.+            if carry {+                // If `integer_part == u64::MAX` and precision < 9, any+                // carry of the overflow during rounding of the+                // `fractional_part` into the `integer_part` will cause the+                // `integer_part` itself to overflow. Avoid this by using an+                // `Option<u64>`, with `None` representing `u64::MAX + 1`.+                integer_part.checked_add(1)             } else {                 Some(integer_part)-            };+            }+        }+    } else {+        Some(integer_part)+    };++    // Determine the end of the buffer: if precision is set, we just+    // use as many digits from the buffer (capped to 9). If it isn't+    // set, we only use all digits up to the last non-zero one.+    let end = f.precision().map(|p| crate::cmp::min(p, 9)).unwrap_or(pos);++    // This closure emits the formatted duration without emitting any+    // padding (padding is calculated below).+    let emit_without_padding = |f: &mut fmt::Formatter<'_>| {+        if let Some(integer_part) = integer_part {+            write!(f, "{}{}", prefix, integer_part)?;+        } else {+            // u64::MAX + 1 == 18446744073709551616+            write!(f, "{}18446744073709551616", prefix)?;+        } -            // Determine the end of the buffer: if precision is set, we just-            // use as many digits from the buffer (capped to 9). If it isn't-            // set, we only use all digits up to the last non-zero one.-            let end = f.precision().map(|p| crate::cmp::min(p, 9)).unwrap_or(pos);+        // Write the decimal point and the fractional part (if any).+        if end > 0 {+            // SAFETY: We are only writing ASCII digits into the buffer and+            // it was initialized with '0's, so it contains valid UTF8.+            let s = unsafe { crate::str::from_utf8_unchecked(&buf[..end]) }; -            // This closure emits the formatted duration without emitting any-            // padding (padding is calculated below).-            let emit_without_padding = |f: &mut fmt::Formatter<'_>| {-                if let Some(integer_part) = integer_part {-                    write!(f, "{}{}", prefix, integer_part)?;-                } else {-                    // u64::MAX + 1 == 18446744073709551616-                    write!(f, "{}18446744073709551616", prefix)?;-                }+            // If the user request a precision > 9, we pad '0's at the end.+            let w = f.precision().unwrap_or(pos);+            write!(f, ".{:0<width$}", s, width = w)?;+        } -                // Write the decimal point and the fractional part (if any).-                if end > 0 {-                    // SAFETY: We are only writing ASCII digits into the buffer and-                    // it was initialized with '0's, so it contains valid UTF8.-                    let s = unsafe { crate::str::from_utf8_unchecked(&buf[..end]) };+        write!(f, "{}", postfix)+    }; -                    // If the user request a precision > 9, we pad '0's at the end.-                    let w = f.precision().unwrap_or(pos);-                    write!(f, ".{:0<width$}", s, width = w)?;+    match f.width() {+        None => {+            // No `width` specified. There's no need to calculate the+            // length of the output in this case, just emit it.+            emit_without_padding(f)+        }+        Some(requested_w) => {+            // A `width` was specified. Calculate the actual width of+            // the output in order to calculate the required padding.+            // It consists of 4 parts:+            // 1. The prefix: is either "+" or "", so we can just use len().+            // 2. The postfix: can be "µs" so we have to count UTF8 characters.+            let mut actual_w = prefix.len() + postfix.chars().count();+            // 3. The integer part:+            if let Some(integer_part) = integer_part {+                if let Some(log) = integer_part.checked_ilog10() {+                    // integer_part is > 0, so has length log10(x)+1+                    actual_w += 1 + log as usize;+                } else {+                    // integer_part is 0, so has length 1.+                    actual_w += 1;                 }+            } else {+                // integer_part is u64::MAX + 1, so has length 20+                actual_w += 20;+            }+            // 4. The fractional part (if any):+            if end > 0 {+                let frac_part_w = f.precision().unwrap_or(pos);+                actual_w += 1 + frac_part_w;+            } -                write!(f, "{}", postfix)-            };--            match f.width() {-                None => {-                    // No `width` specified. There's no need to calculate the-                    // length of the output in this case, just emit it.-                    emit_without_padding(f)-                }-                Some(requested_w) => {-                    // A `width` was specified. Calculate the actual width of-                    // the output in order to calculate the required padding.-                    // It consists of 4 parts:-                    // 1. The prefix: is either "+" or "", so we can just use len().-                    // 2. The postfix: can be "µs" so we have to count UTF8 characters.-                    let mut actual_w = prefix.len() + postfix.chars().count();-                    // 3. The integer part:-                    if let Some(integer_part) = integer_part {-                        if let Some(log) = integer_part.checked_ilog10() {-                            // integer_part is > 0, so has length log10(x)+1-                            actual_w += 1 + log as usize;-                        } else {-                            // integer_part is 0, so has length 1.-                            actual_w += 1;-                        }-                    } else {-                        // integer_part is u64::MAX + 1, so has length 20-                        actual_w += 20;-                    }-                    // 4. The fractional part (if any):-                    if end > 0 {-                        let frac_part_w = f.precision().unwrap_or(pos);-                        actual_w += 1 + frac_part_w;-                    }--                    if requested_w <= actual_w {-                        // Output is already longer than `width`, so don't pad.-                        emit_without_padding(f)-                    } else {-                        // We need to add padding. Use the `Formatter::padding` helper function.-                        let default_align = fmt::Alignment::Left;-                        let post_padding =-                            f.padding((requested_w - actual_w) as u16, default_align)?;-                        emit_without_padding(f)?;-                        post_padding.write(f)-                    }-                }+            if requested_w <= actual_w {+                // Output is already longer than `width`, so don't pad.+                emit_without_padding(f)+            } else {+                // We need to add padding. Use the `Formatter::padding` helper function.+                let default_align = fmt::Alignment::Left;+                let post_padding = f.padding((requested_w - actual_w) as u16, default_align)?;+                emit_without_padding(f)?;+                post_padding.write(f)             }         }+    }+} +#[stable(feature = "duration_debug_impl", since = "1.27.0")]+impl fmt::Debug for Duration {+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {         // Print leading '+' sign if requested         let prefix = if f.sign_plus() { "+" } else { "" }; @@ -1676,6 +1727,38 @@ impl fmt::Debug for Duration {     } } +/// Displays `Duration` in seconds, even if it only contains the nanosecond part.+///+/// This struct is created by [`Duration::display_seconds`].+///+/// # Examples+/// ```+/// #![feature(duration_display_seconds)]+/// use std::time::Duration;+///+/// assert_eq!(Duration::new(2, 0).display_seconds().to_string(), "2s");+/// assert_eq!(Duration::new(0, 2).display_seconds().to_string(), "0.000000002s");+/// ```+#[unstable(feature = "duration_display_seconds", issue = "165108")]+pub struct DisplaySeconds(Duration);++#[unstable(feature = "duration_display_seconds", issue = "165108")]+impl fmt::Debug for DisplaySeconds {+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {+        fmt::Display::fmt(self, f)+    }+}++#[unstable(feature = "duration_display_seconds", issue = "165108")]+impl fmt::Display for DisplaySeconds {+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {+        // Print leading '+' sign if requested+        let prefix = if f.sign_plus() { "+" } else { "" };++        fmt_decimal(f, self.0.secs, self.0.nanos.as_inner(), NANOS_PER_SEC / 10, prefix, "s")+    }+}+ /// An error which can be returned when converting a floating-point value of seconds /// into a [`Duration`]. ///
library/coretests/tests/lib.rs1 + / 0 −
@@ -54,6 +54,7 @@ #![feature(dec2flt)] #![feature(duration_constants)] #![feature(duration_constructors)]+#![feature(duration_display_seconds)] #![feature(exact_div)] #![feature(exact_size_is_empty)] #![feature(extend_one)]
library/coretests/tests/time.rs94 + / 0 −
@@ -488,6 +488,100 @@ fn debug_formatting_round_to_even() {     assert_eq!(format!("{:.2?}", Duration::new(2, 105_000_000)), "2.10s"); } +#[test]+fn display_seconds_precision_zero() {+    assert_eq!(format!("{:.0?}", Duration::new(0, 0).display_seconds()), "0s");+    assert_eq!(format!("{:.0?}", Duration::new(0, 123).display_seconds()), "0s");++    assert_eq!(format!("{:.0?}", Duration::new(0, 000_000_001).display_seconds()), "0s");+    assert_eq!(format!("{:.0?}", Duration::new(0, 499_999_999).display_seconds()), "0s");+    assert_eq!(format!("{:.0?}", Duration::new(0, 500_000_000).display_seconds()), "0s");+    assert_eq!(format!("{:.0?}", Duration::new(0, 999_999_999).display_seconds()), "1s");++    assert_eq!(format!("{:.0?}", Duration::new(1, 000_000_001).display_seconds()), "1s");+    assert_eq!(format!("{:.0?}", Duration::new(1, 499_999_999).display_seconds()), "1s");+    assert_eq!(format!("{:.0?}", Duration::new(1, 500_000_000).display_seconds()), "2s");+    assert_eq!(format!("{:.0?}", Duration::new(1, 999_999_999).display_seconds()), "2s");+}++#[test]+fn display_seconds_precision_two() {+    assert_eq!(format!("{:.2?}", Duration::new(0, 0).display_seconds()), "0.00s");+    assert_eq!(format!("{:.2?}", Duration::new(0, 123).display_seconds()), "0.00s");++    assert_eq!(format!("{:.2?}", Duration::new(0, 000_000_000).display_seconds()), "0.00s");+    assert_eq!(format!("{:.2?}", Duration::new(0, 001_000_000).display_seconds()), "0.00s");+    assert_eq!(format!("{:.2?}", Duration::new(0, 100_000_000).display_seconds()), "0.10s");+    assert_eq!(format!("{:.2?}", Duration::new(0, 104_990_000).display_seconds()), "0.10s");+    assert_eq!(format!("{:.2?}", Duration::new(0, 999_999_999).display_seconds()), "1.00s");++    assert_eq!(format!("{:.2?}", Duration::new(1, 000_000_000).display_seconds()), "1.00s");+    assert_eq!(format!("{:.2?}", Duration::new(4, 001_000_000).display_seconds()), "4.00s");+    assert_eq!(format!("{:.2?}", Duration::new(2, 100_000_000).display_seconds()), "2.10s");+    assert_eq!(format!("{:.2?}", Duration::new(2, 104_990_000).display_seconds()), "2.10s");+    assert_eq!(format!("{:.2?}", Duration::new(8, 999_999_999).display_seconds()), "9.00s");+}++#[test]+fn display_seconds_padding() {+    assert_eq!("0s       ", format!("{:<9?}", Duration::new(0, 0).display_seconds()));+    assert_eq!("       0s", format!("{:>9?}", Duration::new(0, 0).display_seconds()));+    assert_eq!("   0s    ", format!("{:^9?}", Duration::new(0, 0).display_seconds()));+    assert_eq!("0s       ", format!("{:<9.0?}", Duration::new(0, 123).display_seconds()));+    assert_eq!("       0s", format!("{:>9.0?}", Duration::new(0, 123).display_seconds()));+    assert_eq!("   0s    ", format!("{:^9.0?}", Duration::new(0, 123).display_seconds()));+    assert_eq!("0.0071s  ", format!("{:<9?}", Duration::new(0, 7_100_000).display_seconds()));+    assert_eq!("  0.0071s", format!("{:>9?}", Duration::new(0, 7_100_000).display_seconds()));+    assert_eq!(" 0.0071s ", format!("{:^9?}", Duration::new(0, 7_100_000).display_seconds()));+    assert_eq!("123.0s   ", format!("{:<9.1?}", Duration::new(123, 0).display_seconds()));+    assert_eq!("   123.0s", format!("{:>9.1?}", Duration::new(123, 0).display_seconds()));+    assert_eq!(" 123.0s  ", format!("{:^9.1?}", Duration::new(123, 0).display_seconds()));+    assert_eq!("0.999123456s", format!("{:<9?}", Duration::new(0, 999_123_456).display_seconds()));+    assert_eq!("0.999123456s", format!("{:>9?}", Duration::new(0, 999_123_456).display_seconds()));+    assert_eq!("0.999123456s", format!("{:^9?}", Duration::new(0, 999_123_456).display_seconds()));+    assert_eq!("5s       ", format!("{:<9?}", Duration::new(5, 0).display_seconds()));+    assert_eq!("       5s", format!("{:>9?}", Duration::new(5, 0).display_seconds()));+    assert_eq!("   5s    ", format!("{:^9?}", Duration::new(5, 0).display_seconds()));+    assert_eq!("5.000000000000s", format!("{:<9.12?}", Duration::new(5, 0).display_seconds()));+    assert_eq!("5.000000000000s", format!("{:>9.12?}", Duration::new(5, 0).display_seconds()));+    assert_eq!("5.000000000000s", format!("{:^9.12?}", Duration::new(5, 0).display_seconds()));++    // default alignment is left:+    assert_eq!("5s       ", format!("{:9?}", Duration::new(5, 0).display_seconds()));+}++#[test]+fn display_seconds_precision_high() {+    assert_eq!(format!("{:.5?}", Duration::new(0, 236_788_000).display_seconds()), "0.23679s");+    assert_eq!(format!("{:.9?}", Duration::new(0, 000_000_000).display_seconds()), "0.000000000s");+    assert_eq!(format!("{:.9?}", Duration::new(1, 000_000_000).display_seconds()), "1.000000000s");+    assert_eq!(+        format!("{:.10?}", Duration::new(4, 001_000_000).display_seconds()),+        "4.0010000000s"+    );+    assert_eq!(+        format!("{:.20?}", Duration::new(4, 001_000_000).display_seconds()),+        "4.00100000000000000000s"+    );+    assert_eq!(+        format!("{:.10?}", Duration::new(0, 001_000_000).display_seconds()),+        "0.0010000000s"+    );+    assert_eq!(+        format!("{:.20?}", Duration::new(0, 001_000_000).display_seconds()),+        "0.00100000000000000000s"+    );+}++#[test]+fn display_seconds_round_to_even() {+    assert_eq!(format!("{:.0?}", Duration::new(1, 500_000_000).display_seconds()), "2s");+    assert_eq!(format!("{:.0?}", Duration::new(2, 500_000_000).display_seconds()), "2s");+    assert_eq!(format!("{:.3?}", Duration::new(0, 1_500_000).display_seconds()), "0.002s");+    assert_eq!(format!("{:.3?}", Duration::new(0, 2_500_000).display_seconds()), "0.002s");+    assert_eq!(format!("{:.2?}", Duration::new(2, 105_000_000).display_seconds()), "2.10s");+}+ #[test] fn duration_const() {     // test that the methods of `Duration` are usable in a const context