Coverage Report

Created: 2026-09-01 06:51

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/zerovec-0.11.8/src/ule/plain.rs
Line
Count
Source
1
// This file is part of ICU4X. For terms of use, please see the file
2
// called LICENSE at the top level of the ICU4X source tree
3
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4
5
#![allow(clippy::upper_case_acronyms)]
6
//! ULE implementation for Plain Old Data types, including all sized integers.
7
8
use super::*;
9
use crate::impl_ule_from_array;
10
use crate::ZeroSlice;
11
use core::num::{NonZeroI8, NonZeroU8};
12
13
/// A u8 array of little-endian data with infallible conversions to and from &[u8].
14
#[repr(transparent)]
15
#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Hash)]
16
#[allow(clippy::exhaustive_structs)] // newtype
17
pub struct RawBytesULE<const N: usize>(pub [u8; N]);
18
19
impl<const N: usize> RawBytesULE<N> {
20
    #[inline]
21
0
    pub fn as_bytes(&self) -> &[u8] {
22
0
        &self.0
23
0
    }
24
25
    #[inline]
26
0
    pub fn from_bytes_unchecked_mut(bytes: &mut [u8]) -> &mut [Self] {
27
0
        let data = bytes.as_mut_ptr();
28
0
        let len = bytes.len() / N;
29
        // Safe because Self is transparent over [u8; N]
30
0
        unsafe { slice::from_raw_parts_mut(data as *mut Self, len) }
31
0
    }
32
}
33
34
// Safety (based on the safety checklist on the ULE trait):
35
//  1. RawBytesULE does not include any uninitialized or padding bytes.
36
//     (achieved by `#[repr(transparent)]` on a type that satisfies this invariant)
37
//  2. RawBytesULE is aligned to 1 byte.
38
//     (achieved by `#[repr(transparent)]` on a type that satisfies this invariant)
39
//  3. The impl of validate_bytes() returns an error if any byte is not valid (never).
40
//  4. The impl of validate_bytes() returns an error if there are leftover bytes.
41
//  5. The other ULE methods use the default impl.
42
//  6. RawBytesULE byte equality is semantic equality
43
unsafe impl<const N: usize> ULE for RawBytesULE<N> {
44
    #[inline]
45
0
    fn validate_bytes(bytes: &[u8]) -> Result<(), UleError> {
46
0
        if bytes.len() % N == 0 {
47
            // Safe because Self is transparent over [u8; N]
48
0
            Ok(())
49
        } else {
50
0
            Err(UleError::length::<Self>(bytes.len()))
51
        }
52
0
    }
Unexecuted instantiation: <zerovec::ule::plain::RawBytesULE<2> as zerovec::ule::ULE>::validate_bytes
Unexecuted instantiation: <zerovec::ule::plain::RawBytesULE<_> as zerovec::ule::ULE>::validate_bytes
53
}
54
55
impl<const N: usize> From<[u8; N]> for RawBytesULE<N> {
56
    #[inline]
57
0
    fn from(le_bytes: [u8; N]) -> Self {
58
0
        Self(le_bytes)
59
0
    }
60
}
61
62
macro_rules! impl_numbers_with_raw_bytes_ule {
63
    ($unsigned:ty, $signed:ty $(, $float:ty)?) => {
64
        const _: () = assert!(size_of::<$unsigned>() == size_of::<$signed>() $(&& size_of::<$unsigned>() == size_of::<$float>())?);
65
66
        impl RawBytesULE<{ size_of::<$unsigned>() }> {
67
            #[doc = concat!("Gets this `RawBytesULE` as a `", stringify!($unsigned), "`. This is equivalent to calling [`AsULE::from_unaligned()`] on [`", stringify!($unsigned), "`].")]
68
            #[inline]
69
0
            pub const fn as_unsigned_int(&self) -> $unsigned {
70
0
                <$unsigned>::from_le_bytes(self.0)
71
0
            }
Unexecuted instantiation: <zerovec::ule::plain::RawBytesULE<2>>::as_unsigned_int
Unexecuted instantiation: <zerovec::ule::plain::RawBytesULE<2>>::as_unsigned_int
Unexecuted instantiation: <zerovec::ule::plain::RawBytesULE<16>>::as_unsigned_int
Unexecuted instantiation: <zerovec::ule::plain::RawBytesULE<4>>::as_unsigned_int
Unexecuted instantiation: <zerovec::ule::plain::RawBytesULE<8>>::as_unsigned_int
Unexecuted instantiation: <zerovec::ule::plain::RawBytesULE<2>>::as_unsigned_int
72
73
            #[doc = concat!("Gets this `RawBytesULE` as a `", stringify!($unsigned), "`. This is equivalent to calling [`AsULE::from_unaligned()`] on [`", stringify!($signed), "`].")]
74
            #[inline]
75
0
            pub const fn as_signed_int(&self) -> $signed {
76
0
                <$signed>::from_le_bytes(self.0)
77
0
            }
Unexecuted instantiation: <zerovec::ule::plain::RawBytesULE<16>>::as_signed_int
Unexecuted instantiation: <zerovec::ule::plain::RawBytesULE<4>>::as_signed_int
Unexecuted instantiation: <zerovec::ule::plain::RawBytesULE<8>>::as_signed_int
Unexecuted instantiation: <zerovec::ule::plain::RawBytesULE<2>>::as_signed_int
78
79
            $(
80
                #[doc = concat!("Gets this `RawBytesULE` as a `", stringify!($float), "`. This is equivalent to calling [`AsULE::from_unaligned()`] on [`", stringify!($float), "`].")]
81
                #[inline]
82
0
                pub const fn as_float(&self) -> $float {
83
0
                    <$float>::from_le_bytes(self.0)
84
0
                }
Unexecuted instantiation: <zerovec::ule::plain::RawBytesULE<4>>::as_float
Unexecuted instantiation: <zerovec::ule::plain::RawBytesULE<8>>::as_float
85
            )?
86
87
            #[doc = concat!("Converts a `", stringify!($unsigned), "` to a `RawBytesULE`. This is equivalent to calling [`AsULE::to_unaligned()`] on [`", stringify!($unsigned), "`].")]
88
            #[inline]
89
0
            pub const fn from_aligned(value: $unsigned) -> Self {
90
0
                Self(value.to_le_bytes())
91
0
            }
Unexecuted instantiation: <zerovec::ule::plain::RawBytesULE<16>>::from_aligned
Unexecuted instantiation: <zerovec::ule::plain::RawBytesULE<4>>::from_aligned
Unexecuted instantiation: <zerovec::ule::plain::RawBytesULE<8>>::from_aligned
Unexecuted instantiation: <zerovec::ule::plain::RawBytesULE<2>>::from_aligned
92
93
            impl_ule_from_array!(
94
                $unsigned,
95
                RawBytesULE<{ size_of::<$unsigned>() }>,
96
                RawBytesULE([0; { size_of::<$unsigned>() }])
97
            );
98
        }
99
100
        impl_byte_slice_type!(from_unsigned, $unsigned);
101
        impl_const_constructors!($unsigned);
102
103
        impl_byte_slice_type!(from_signed, $signed);
104
        impl_const_constructors!($signed);
105
106
        $(
107
            // These impls are actually safe and portable due to Rust always using IEEE 754, see the documentation
108
            // on f32::from_le_bytes: https://doc.rust-lang.org/stable/std/primitive.f32.html#method.from_le_bytes
109
            //
110
            // The only potential problem is that some older platforms treat signaling NaNs differently. This is
111
            // still quite portable, signalingness is not typically super important.
112
113
            // The from_bits documentation mentions that they have identical byte representations to integers
114
            // and EqULE only cares about LE systems
115
            impl_byte_slice_type!(from_float, $float);
116
            impl_const_constructors!($float);
117
        )?
118
};
119
}
120
121
macro_rules! impl_const_constructors {
122
    ($base:ty) => {
123
        impl ZeroSlice<$base> {
124
            /// This function can be used for constructing [`ZeroVec`](crate::ZeroVec)s in a `const` context, avoiding
125
            /// parsing checks.
126
            ///
127
            /// This cannot be generic over `T` because of current limitations in `const`, but if
128
            /// this method is needed in a non-`const` context, check out [`ZeroSlice::parse_bytes()`]
129
            /// instead.
130
            ///
131
            /// See [`ZeroSlice::cast()`] for an example.
132
0
            pub const fn try_from_bytes(bytes: &[u8]) -> Result<&Self, UleError> {
133
0
                let len = bytes.len();
134
                const STRIDE: usize = size_of::<$base>();
135
                #[allow(clippy::modulo_one)]
136
0
                if (if STRIDE <= 1 { len } else { len % STRIDE }) == 0 {
137
0
                    Ok(unsafe { Self::from_bytes_unchecked(bytes) })
138
                } else {
139
0
                    Err(UleError::InvalidLength {
140
0
                        ty: concat!("<const construct: ", stringify!($base), ">"),
141
0
                        len,
142
0
                    })
143
                }
144
0
            }
Unexecuted instantiation: <zerovec::zerovec::slice::ZeroSlice<i64>>::try_from_bytes
Unexecuted instantiation: <zerovec::zerovec::slice::ZeroSlice<f64>>::try_from_bytes
Unexecuted instantiation: <zerovec::zerovec::slice::ZeroSlice<u128>>::try_from_bytes
Unexecuted instantiation: <zerovec::zerovec::slice::ZeroSlice<i128>>::try_from_bytes
Unexecuted instantiation: <zerovec::zerovec::slice::ZeroSlice<u8>>::try_from_bytes
Unexecuted instantiation: <zerovec::zerovec::slice::ZeroSlice<bool>>::try_from_bytes
Unexecuted instantiation: <zerovec::zerovec::slice::ZeroSlice<()>>::try_from_bytes
Unexecuted instantiation: <zerovec::zerovec::slice::ZeroSlice<i16>>::try_from_bytes
Unexecuted instantiation: <zerovec::zerovec::slice::ZeroSlice<u32>>::try_from_bytes
Unexecuted instantiation: <zerovec::zerovec::slice::ZeroSlice<i32>>::try_from_bytes
Unexecuted instantiation: <zerovec::zerovec::slice::ZeroSlice<f32>>::try_from_bytes
Unexecuted instantiation: <zerovec::zerovec::slice::ZeroSlice<u64>>::try_from_bytes
Unexecuted instantiation: <zerovec::zerovec::slice::ZeroSlice<u16>>::try_from_bytes
145
        }
146
    };
147
}
148
149
macro_rules! impl_byte_slice_type {
150
    ($single_fn:ident, $type:ty) => {
151
        impl From<$type> for RawBytesULE<{ size_of::<$type>() }> {
152
            #[inline]
153
0
            fn from(value: $type) -> Self {
154
0
                Self(value.to_le_bytes())
155
0
            }
Unexecuted instantiation: <zerovec::ule::plain::RawBytesULE<8> as core::convert::From<f64>>::from
Unexecuted instantiation: <zerovec::ule::plain::RawBytesULE<16> as core::convert::From<u128>>::from
Unexecuted instantiation: <zerovec::ule::plain::RawBytesULE<16> as core::convert::From<i128>>::from
Unexecuted instantiation: <zerovec::ule::plain::RawBytesULE<4> as core::convert::From<u32>>::from
Unexecuted instantiation: <zerovec::ule::plain::RawBytesULE<4> as core::convert::From<i32>>::from
Unexecuted instantiation: <zerovec::ule::plain::RawBytesULE<4> as core::convert::From<f32>>::from
Unexecuted instantiation: <zerovec::ule::plain::RawBytesULE<8> as core::convert::From<u64>>::from
Unexecuted instantiation: <zerovec::ule::plain::RawBytesULE<8> as core::convert::From<i64>>::from
Unexecuted instantiation: <zerovec::ule::plain::RawBytesULE<2> as core::convert::From<u16>>::from
Unexecuted instantiation: <zerovec::ule::plain::RawBytesULE<2> as core::convert::From<i16>>::from
156
        }
157
        impl AsULE for $type {
158
            type ULE = RawBytesULE<{ size_of::<$type>() }>;
159
            #[inline]
160
0
            fn to_unaligned(self) -> Self::ULE {
161
0
                RawBytesULE(self.to_le_bytes())
162
0
            }
Unexecuted instantiation: <u16 as zerovec::ule::AsULE>::to_unaligned
Unexecuted instantiation: <u32 as zerovec::ule::AsULE>::to_unaligned
Unexecuted instantiation: <u16 as zerovec::ule::AsULE>::to_unaligned
Unexecuted instantiation: <f64 as zerovec::ule::AsULE>::to_unaligned
Unexecuted instantiation: <u128 as zerovec::ule::AsULE>::to_unaligned
Unexecuted instantiation: <i128 as zerovec::ule::AsULE>::to_unaligned
Unexecuted instantiation: <u32 as zerovec::ule::AsULE>::to_unaligned
Unexecuted instantiation: <i32 as zerovec::ule::AsULE>::to_unaligned
Unexecuted instantiation: <f32 as zerovec::ule::AsULE>::to_unaligned
Unexecuted instantiation: <u64 as zerovec::ule::AsULE>::to_unaligned
Unexecuted instantiation: <i64 as zerovec::ule::AsULE>::to_unaligned
Unexecuted instantiation: <u16 as zerovec::ule::AsULE>::to_unaligned
Unexecuted instantiation: <i16 as zerovec::ule::AsULE>::to_unaligned
163
            #[inline]
164
0
            fn from_unaligned(unaligned: Self::ULE) -> Self {
165
0
                <$type>::from_le_bytes(unaligned.0)
166
0
            }
Unexecuted instantiation: <u32 as zerovec::ule::AsULE>::from_unaligned
Unexecuted instantiation: <u16 as zerovec::ule::AsULE>::from_unaligned
Unexecuted instantiation: <u16 as zerovec::ule::AsULE>::from_unaligned
Unexecuted instantiation: <u32 as zerovec::ule::AsULE>::from_unaligned
Unexecuted instantiation: <u16 as zerovec::ule::AsULE>::from_unaligned
Unexecuted instantiation: <u32 as zerovec::ule::AsULE>::from_unaligned
Unexecuted instantiation: <u16 as zerovec::ule::AsULE>::from_unaligned
Unexecuted instantiation: <f64 as zerovec::ule::AsULE>::from_unaligned
Unexecuted instantiation: <u128 as zerovec::ule::AsULE>::from_unaligned
Unexecuted instantiation: <i128 as zerovec::ule::AsULE>::from_unaligned
Unexecuted instantiation: <u32 as zerovec::ule::AsULE>::from_unaligned
Unexecuted instantiation: <i32 as zerovec::ule::AsULE>::from_unaligned
Unexecuted instantiation: <f32 as zerovec::ule::AsULE>::from_unaligned
Unexecuted instantiation: <u64 as zerovec::ule::AsULE>::from_unaligned
Unexecuted instantiation: <i64 as zerovec::ule::AsULE>::from_unaligned
Unexecuted instantiation: <u16 as zerovec::ule::AsULE>::from_unaligned
Unexecuted instantiation: <i16 as zerovec::ule::AsULE>::from_unaligned
167
        }
168
        // EqULE is true because $type and RawBytesULE<{ size_of::<$type> }>
169
        // have the same byte sequence on little-endian
170
        unsafe impl EqULE for $type {}
171
172
        impl RawBytesULE<{ size_of::<$type>() }> {
173
0
            pub const fn $single_fn(v: $type) -> Self {
174
0
                RawBytesULE(v.to_le_bytes())
175
0
            }
Unexecuted instantiation: <zerovec::ule::plain::RawBytesULE<8>>::from_signed
Unexecuted instantiation: <zerovec::ule::plain::RawBytesULE<8>>::from_float
Unexecuted instantiation: <zerovec::ule::plain::RawBytesULE<16>>::from_unsigned
Unexecuted instantiation: <zerovec::ule::plain::RawBytesULE<16>>::from_signed
Unexecuted instantiation: <zerovec::ule::plain::RawBytesULE<2>>::from_signed
Unexecuted instantiation: <zerovec::ule::plain::RawBytesULE<4>>::from_unsigned
Unexecuted instantiation: <zerovec::ule::plain::RawBytesULE<4>>::from_signed
Unexecuted instantiation: <zerovec::ule::plain::RawBytesULE<4>>::from_float
Unexecuted instantiation: <zerovec::ule::plain::RawBytesULE<8>>::from_unsigned
Unexecuted instantiation: <zerovec::ule::plain::RawBytesULE<2>>::from_unsigned
176
        }
177
    };
178
}
179
180
impl_numbers_with_raw_bytes_ule!(u16, i16);
181
impl_numbers_with_raw_bytes_ule!(u32, i32, f32);
182
impl_numbers_with_raw_bytes_ule!(u64, i64, f64);
183
impl_numbers_with_raw_bytes_ule!(u128, i128);
184
185
// Safety (based on the safety checklist on the ULE trait):
186
//  1. u8 does not include any uninitialized or padding bytes.
187
//  2. u8 is aligned to 1 byte.
188
//  3. The impl of validate_bytes() returns an error if any byte is not valid (never).
189
//  4. The impl of validate_bytes() returns an error if there are leftover bytes (never).
190
//  5. The other ULE methods use the default impl.
191
//  6. u8 byte equality is semantic equality
192
unsafe impl ULE for u8 {
193
    #[inline]
194
0
    fn validate_bytes(_bytes: &[u8]) -> Result<(), UleError> {
195
0
        Ok(())
196
0
    }
Unexecuted instantiation: <u8 as zerovec::ule::ULE>::validate_bytes
Unexecuted instantiation: <u8 as zerovec::ule::ULE>::validate_bytes
197
}
198
199
impl AsULE for u8 {
200
    type ULE = Self;
201
    #[inline]
202
0
    fn to_unaligned(self) -> Self::ULE {
203
0
        self
204
0
    }
Unexecuted instantiation: <u8 as zerovec::ule::AsULE>::to_unaligned
Unexecuted instantiation: <u8 as zerovec::ule::AsULE>::to_unaligned
205
    #[inline]
206
0
    fn from_unaligned(unaligned: Self::ULE) -> Self {
207
0
        unaligned
208
0
    }
Unexecuted instantiation: <u8 as zerovec::ule::AsULE>::from_unaligned
Unexecuted instantiation: <u8 as zerovec::ule::AsULE>::from_unaligned
Unexecuted instantiation: <u8 as zerovec::ule::AsULE>::from_unaligned
209
}
210
211
// EqULE is true because u8 is its own ULE.
212
unsafe impl EqULE for u8 {}
213
214
impl_const_constructors!(u8);
215
216
// Safety (based on the safety checklist on the ULE trait):
217
//  1. NonZeroU8 does not include any uninitialized or padding bytes.
218
//  2. NonZeroU8 is aligned to 1 byte.
219
//  3. The impl of validate_bytes() returns an error if any byte is not valid (0x00).
220
//  4. The impl of validate_bytes() returns an error if there are leftover bytes (never).
221
//  5. The other ULE methods use the default impl.
222
//  6. NonZeroU8 byte equality is semantic equality
223
unsafe impl ULE for NonZeroU8 {
224
    #[inline]
225
0
    fn validate_bytes(bytes: &[u8]) -> Result<(), UleError> {
226
0
        bytes.iter().try_for_each(|b| {
227
0
            if *b == 0x00 {
228
0
                Err(UleError::parse::<Self>())
229
            } else {
230
0
                Ok(())
231
            }
232
0
        })
233
0
    }
234
}
235
236
impl AsULE for NonZeroU8 {
237
    type ULE = Self;
238
    #[inline]
239
0
    fn to_unaligned(self) -> Self::ULE {
240
0
        self
241
0
    }
242
    #[inline]
243
0
    fn from_unaligned(unaligned: Self::ULE) -> Self {
244
0
        unaligned
245
0
    }
246
}
247
248
unsafe impl EqULE for NonZeroU8 {}
249
250
impl NicheBytes<1> for NonZeroU8 {
251
    const NICHE_BIT_PATTERN: [u8; 1] = [0x00];
252
}
253
254
// Safety (based on the safety checklist on the ULE trait):
255
//  1. i8 does not include any uninitialized or padding bytes.
256
//  2. i8 is aligned to 1 byte.
257
//  3. The impl of validate_bytes() returns an error if any byte is not valid (never).
258
//  4. The impl of validate_bytes() returns an error if there are leftover bytes (never).
259
//  5. The other ULE methods use the default impl.
260
//  6. i8 byte equality is semantic equality
261
unsafe impl ULE for i8 {
262
    #[inline]
263
0
    fn validate_bytes(_bytes: &[u8]) -> Result<(), UleError> {
264
0
        Ok(())
265
0
    }
266
}
267
268
impl AsULE for i8 {
269
    type ULE = Self;
270
    #[inline]
271
0
    fn to_unaligned(self) -> Self::ULE {
272
0
        self
273
0
    }
274
    #[inline]
275
0
    fn from_unaligned(unaligned: Self::ULE) -> Self {
276
0
        unaligned
277
0
    }
278
}
279
280
// EqULE is true because i8 is its own ULE.
281
unsafe impl EqULE for i8 {}
282
283
impl AsULE for NonZeroI8 {
284
    type ULE = NonZeroU8;
285
    #[inline]
286
0
    fn to_unaligned(self) -> Self::ULE {
287
        // TODO: use cast_signed at 1.87 MSRV
288
        // Safety: .get() is non-zero
289
0
        unsafe { NonZeroU8::new_unchecked(self.get() as u8) }
290
0
    }
291
292
    #[inline]
293
0
    fn from_unaligned(unaligned: Self::ULE) -> Self {
294
        // TODO: use cast_unsigned at 1.87 MSRV
295
        // Safety: .get() is non-zero
296
0
        unsafe { NonZeroI8::new_unchecked(unaligned.get() as i8) }
297
0
    }
298
}
299
300
// The bool impl is not as efficient as it could be
301
// We can, in the future, have https://github.com/unicode-org/icu4x/blob/main/utils/zerovec/design_doc.md#bitpacking
302
// for better bitpacking
303
304
// Safety (based on the safety checklist on the ULE trait):
305
//  1. bool does not include any uninitialized or padding bytes (the remaining 7 bytes in bool are by definition zero)
306
//  2. bool is aligned to 1 byte.
307
//  3. The impl of validate_bytes() returns an error if any byte is not valid (bytes that are not 0 or 1).
308
//  4. The impl of validate_bytes() returns an error if there are leftover bytes (never).
309
//  5. The other ULE methods use the default impl.
310
//  6. bool byte equality is semantic equality
311
unsafe impl ULE for bool {
312
    #[inline]
313
0
    fn validate_bytes(bytes: &[u8]) -> Result<(), UleError> {
314
0
        for byte in bytes {
315
            // https://doc.rust-lang.org/reference/types/boolean.html
316
            // Rust booleans are always size 1, align 1 values with valid bit patterns 0x0 or 0x1
317
0
            if *byte > 1 {
318
0
                return Err(UleError::parse::<Self>());
319
0
            }
320
        }
321
0
        Ok(())
322
0
    }
323
}
324
325
impl AsULE for bool {
326
    type ULE = Self;
327
    #[inline]
328
0
    fn to_unaligned(self) -> Self::ULE {
329
0
        self
330
0
    }
331
    #[inline]
332
0
    fn from_unaligned(unaligned: Self::ULE) -> Self {
333
0
        unaligned
334
0
    }
335
}
336
337
// EqULE is true because bool is its own ULE.
338
unsafe impl EqULE for bool {}
339
340
impl_const_constructors!(bool);
341
342
// Safety (based on the safety checklist on the ULE trait):
343
//  1. () does not include any uninitialized or padding bytes (it has no bytes)
344
//  2. () is a ZST that is safe to construct
345
//  3. The impl of validate_bytes() returns an error if any byte is not valid (any byte).
346
//  4. The impl of validate_bytes() returns an error if there are leftover bytes (always).
347
//  5. The other ULE methods use the default impl.
348
//  6. () byte equality is semantic equality
349
unsafe impl ULE for () {
350
    #[inline]
351
0
    fn validate_bytes(bytes: &[u8]) -> Result<(), UleError> {
352
0
        if bytes.is_empty() {
353
0
            Ok(())
354
        } else {
355
0
            Err(UleError::length::<Self>(bytes.len()))
356
        }
357
0
    }
358
}
359
360
impl AsULE for () {
361
    type ULE = Self;
362
    #[inline]
363
0
    fn to_unaligned(self) -> Self::ULE {
364
0
        self
365
0
    }
366
    #[inline]
367
0
    fn from_unaligned(unaligned: Self::ULE) -> Self {
368
0
        unaligned
369
0
    }
370
}
371
372
// EqULE is true because () is its own ULE.
373
unsafe impl EqULE for () {}
374
375
impl_const_constructors!(());