Skip to main content

datasize/
lib.rs

1//! Heap data estimator.
2//!
3//! The `datasize` crate allows estimating the amount of heap memory used by a value. It does so by
4//! providing or deriving an implementation of the `DataSize` trait, which knows how to calculate
5//! the size for many `std` types and primitives.
6//!
7//! The aim is to get a reasonable approximation of memory usage, especially with variably sized
8//! types like `Vec`s. While it is acceptable to be a few bytes off in some cases, any user should
9//! be able to easily tell whether their memory is growing linearly or logarithmically by glancing
10//! at the reported numbers.
11//!
12//! The crate does not take alignment or memory layouts into account, or unusual behavior or
13//! optimizations of allocators. It is depending entirely on the data inside the type, thus the name
14//! of the crate.
15//!
16//! # General usage
17//!
18//! For any type that implements `DataSize`, the `data_size` convenience function can be used to
19//! guess the size of its heap allocation:
20//!
21//! ```rust
22//! use datasize::data_size;
23//!
24//! let data: Vec<u64> = vec![1, 2, 3];
25//! #[cfg(feature = "std")]
26//! assert_eq!(data_size(&data), 24);
27//! ```
28//!
29//! Types implementing the trait also provide two additional constants, `IS_DYNAMIC` and
30//! `STATIC_HEAP_SIZE`.
31//!
32//! `IS_DYNAMIC` indicates whether a value's size can change over time:
33//!
34//! ```rust
35//! use datasize::DataSize;
36//!
37//! #[cfg(feature = "std")]
38//! // A `Vec` of any kind may have elements added or removed, so it changes size.
39//! assert!(Vec::<u64>::IS_DYNAMIC);
40//!
41//! // The elements of type `u64` in it are not dynamic. This allows the implementation to
42//! // simply estimate the size as number_of_elements * size_of::<u64>.
43//! assert!(!u64::IS_DYNAMIC);
44//! ```
45//!
46//! Additionally, `STATIC_HEAP_SIZE` indicates the amount of heap memory a type will always use. A
47//! good example is a `Box<u64>` -- it will always use 8 bytes of heap memory, but not change in
48//! size:
49//!
50//!
51//! ```rust
52//! use datasize::DataSize;
53//!
54//! #[cfg(feature = "std")]
55//! assert_eq!(Box::<u64>::STATIC_HEAP_SIZE, 8);
56//! #[cfg(feature = "std")]
57//! assert!(!Box::<u64>::IS_DYNAMIC);
58//! ```
59//!
60//! # Overriding derived data size calculation for single fields.
61//!
62//! On structs (but not enums!) the calculation for heap size can be overriden for single fields,
63//! which is useful when dealing with third-party crates whose fields do not implement `DataSize` by
64//! simply annotating it with `#[data_size(with = ...)]` and pointing to a `Fn(T) -> usize`
65//! function:
66//!
67//! ```rust
68//! use datasize::DataSize;
69//!
70//! // Let's pretend this type is from a foreign crate.
71//! struct ThirdPartyType;
72//!
73//! fn estimate_third_party_type(value: &Vec<ThirdPartyType>) -> usize {
74//!     // We assume every item is 512 bytes in heap size.
75//!     value.len() * 512
76//! }
77//!
78//! #[cfg(feature = "std")]
79//! #[derive(DataSize)]
80//! struct MyStruct {
81//!     items: Vec<u32>,
82//!     #[data_size(with = estimate_third_party_type)]
83//!     other_stuff: Vec<ThirdPartyType>,
84//! }
85//! ```
86//!
87//! This automatically marks the whole struct as always dynamic, so the custom estimation function
88//! is called every time `MyStruct` is sized.
89//!
90//! # Implementing `DataSize` for custom types
91//!
92//! The `DataSize` trait can be implemented for custom types manually:
93//!
94//! ```rust
95//! # use datasize::{DataSize, data_size};
96//! struct MyType {
97//!     items: Vec<i64>,
98//!     flag: bool,
99//!     counter: Box<u64>,
100//! }
101//!
102//! #[cfg(feature = "std")]
103//! impl DataSize for MyType {
104//!     // `MyType` contains a `Vec`, so `IS_DYNAMIC` is set to true.
105//!     const IS_DYNAMIC: bool = true;
106//!
107//!     // The only always present heap item is the `counter` value, which is 8 bytes.
108//!     const STATIC_HEAP_SIZE: usize = 8;
109//!
110//!     #[inline]
111//!     fn estimate_heap_size(&self) -> usize {
112//!         // We can be lazy here and delegate to all the existing implementations:
113//!         data_size(&self.items) + data_size(&self.flag) + data_size(&self.counter)
114//!     }
115//! }
116//!
117//! let my_data = MyType {
118//!     items: vec![1, 2, 3],
119//!     flag: true,
120//!     counter: Box::new(42),
121//! };
122//!
123//! #[cfg(feature = "std")]
124//! // Three i64 and one u64 on the heap sum up to 32 bytes:
125//! assert_eq!(data_size(&my_data), 32);
126//! ```
127//!
128//! Since implementing this for `struct` types is cumbersome and repetitive, the crate provides a
129//! `DataSize` macro for convenience:
130//!
131//! ```
132//! # use datasize::{DataSize, data_size};
133//! // Equivalent to the manual implementation above:
134//! #[cfg(feature = "std")]
135//! #[derive(DataSize)]
136//! struct MyType {
137//!     items: Vec<i64>,
138//!     flag: bool,
139//!     counter: Box<u64>,
140//! }
141//! # #[cfg(feature = "std")]
142//! # let my_data = MyType {
143//! #     items: vec![1, 2, 3],
144//! #     flag: true,
145//! #     counter: Box::new(42),
146//! # };
147//! # #[cfg(feature = "std")]
148//! # assert_eq!(data_size(&my_data), 32);
149//! ```
150//!
151//! See the `DataSize` macro documentation in the `datasize_derive` crate for details.
152//!
153//! ## Performance considerations
154//!
155//! Determining the full size of data can be quite expensive, especially if multiple nested levels
156//! of dynamic types are used. The crate uses `IS_DYNAMIC` and `STATIC_HEAP_SIZE` to optimize when
157//! it can, so in many cases not every element of a vector needs to be checked individually.
158//!
159//! However, if the contained types are dynamic, every element must (and will) be checked, so keep
160//! this in mind when performance is an issue.
161//!
162//! ## Handlings references, `Arc`s and similar types
163//!
164//! Any reference will be counted as having a data size of 0, as it does not own the value. There
165//! are some special reference-like types like `Arc`, which are discussed below.
166//!
167//! ### `Arc` and `Rc`
168//!
169//! Currently `Arc`s are not supported. A planned development is to allow users to mark an instance
170//! of an `Arc` as "primary" and have its heap memory usage counted, but currently this is not
171//! implemented.
172//!
173//! Any `Arc` will be estimated to have a heap size of `0`, to avoid cycles resulting in infinite
174//! loops.
175//!
176//! The `Rc` type is handled in the same manner.
177//!
178//! ## Additional types
179//!
180//! Some additional types from external crates are available behind feature flags.
181//!
182//! * `fake_clock-types`: Support for the `fake_instant::FakeClock` type.
183//! * `futures-types`: Some types from the `futures` crate.
184//! * `smallvec-types`: Support for the `smallvec::SmallVec` type.
185//! * `tokio-types`: Some types from the `tokio` crate.
186//!
187//! ## `no_std` support
188//!
189//! Although slightly paradoxical due to the fact that without `std` or at least `alloc` there won't
190//! be any heap in most cases, the crate supports a `no_std` environment. Disabling the "std"
191//! feature (by disabling default features) will produce a version of the crate that does not rely
192//! on the standard library. This can be used to derive the `DataSize` trait for types without
193//! boilerplate, even though their heap size will usually be 0.
194//!
195//! ## Arrays and const generics
196//!
197//! By default, this crate requires at least Rust version 1.51.0, in order to implement DataSize
198//! for [T; N] arrays generically. This implementation is provided by the "const-generics"
199//! feature flag, which is enabled by default. In order to use an older Rust version,
200//! you can specify [`default-features = false`](https://doc.rust-lang.org/cargo/reference/features.html#dependency-features) and `features = ["std"]` for `datasize` in your Cargo.toml.
201//!
202//! When the `const-generics` feature flag is disabled, a DataSize implementation will be provided
203//! for arrays of small sizes, and for some larger sizes related to powers of 2.
204//!
205//! ## Known issues
206//!
207//! The derive macro currently does not support generic structs with inline type bounds, e.g.
208//!
209//! ```ignore
210//! struct Foo<T: Copy> { ... }
211//! ```
212//!
213//! This can be worked around by using an equivalent `where` clause:
214//!
215//! ```ignore
216//! struct Foo<T>
217//! where T: Copy
218//! { ... }
219//! ```
220
221#![cfg_attr(not(feature = "std"), no_std)]
222#![allow(clippy::assertions_on_constants)]
223
224#[cfg(feature = "fake_clock-types")]
225mod fake_clock;
226#[cfg(feature = "futures-types")]
227mod futures;
228#[cfg(feature = "smallvec-types")]
229mod smallvec;
230#[cfg(feature = "std")]
231mod std;
232#[cfg(feature = "tokio-types")]
233mod tokio;
234
235pub use datasize_derive::DataSize;
236
237/// A `const fn` variant of the `min` function.
238pub const fn min(a: usize, b: usize) -> usize {
239    [a, b][(a > b) as usize]
240}
241
242/// Indicates that a type knows how to approximate its memory usage.
243pub trait DataSize {
244    /// If `true`, the type has a heap size that can vary at runtime, depending on the actual value.
245    const IS_DYNAMIC: bool;
246
247    /// The amount of space a value of the type _always_ occupies. If `IS_DYNAMIC` is false, this is
248    /// the total amount of heap memory occupied by the value. Otherwise this is a lower bound.
249    const STATIC_HEAP_SIZE: usize;
250
251    /// Estimates the size of heap memory taken up by this value.
252    ///
253    /// Does not include data on the stack, which is usually determined using `mem::size_of`.
254    fn estimate_heap_size(&self) -> usize;
255
256    #[cfg(feature = "detailed")]
257    /// Create a tree of memory estimations.
258    ///
259    /// Similar to `estimate_heap_size`, but the returned value is a tree that typically reports
260    /// memory used by structs individually.
261    ///
262    /// Requires the `detailed` feature to be enabled.
263    #[inline]
264    fn estimate_detailed_heap_size(&self) -> MemUsageNode {
265        MemUsageNode::Size(self.estimate_heap_size())
266    }
267}
268
269#[cfg(feature = "detailed")]
270/// A node in a memory reporting tree.
271#[derive(Debug, serde::Serialize, PartialEq)]
272pub enum MemUsageNode {
273    Size(usize),
274    Detailed(::std::collections::HashMap<&'static str, MemUsageNode>),
275}
276
277#[cfg(feature = "detailed")]
278impl MemUsageNode {
279    /// Calculate the total memory usage given by detailed estimate
280    #[inline]
281    pub fn total(&self) -> usize {
282        match self {
283            MemUsageNode::Size(sz) => *sz,
284            MemUsageNode::Detailed(members) => members.values().map(MemUsageNode::total).sum(),
285        }
286    }
287}
288
289/// Estimates allocated heap data from data of value.
290///
291/// Checks if `T` is dynamic; if it is not, returns `T::STATIC_HEAP_SIZE`. Otherwise delegates to
292/// `T::estimate_heap_size`.
293#[inline]
294pub fn data_size<T: ?Sized>(value: &T) -> usize
295where
296    T: DataSize,
297{
298    value.estimate_heap_size()
299}
300
301#[cfg(feature = "detailed")]
302/// Estimates allocated heap data from data of value.
303#[inline]
304pub fn data_size_detailed<T: ?Sized>(value: &T) -> MemUsageNode
305where
306    T: DataSize,
307{
308    value.estimate_detailed_heap_size()
309}
310
311/// Helper macro to define a heap size for one or more non-dynamic types.
312#[macro_export]
313macro_rules! non_dynamic_const_heap_size {
314    ($($ty:ty)*, $sz:expr) => {
315        $(impl DataSize for $ty {
316            const IS_DYNAMIC: bool = false;
317            const STATIC_HEAP_SIZE: usize = $sz;
318
319            #[inline]
320            fn estimate_heap_size(&self) -> usize {
321                $sz
322            }
323        })*
324    };
325}
326
327// Hack to allow `+` to be used to join macro arguments.
328macro_rules! strip_plus {
329    (+ $($rest: tt)*) => {
330        $($rest)*
331    }
332}
333
334macro_rules! tuple_heap_size {
335    ($($n:tt $name:ident);+) => {
336        impl<$($name),*> DataSize for ($($name),*)
337        where $($name: DataSize),*
338        {
339            const IS_DYNAMIC: bool = $($name::IS_DYNAMIC)|*;
340
341            const STATIC_HEAP_SIZE: usize =
342                strip_plus!($(+ $name::STATIC_HEAP_SIZE)+);
343
344            #[inline]
345            fn estimate_heap_size(&self) -> usize {
346                strip_plus!($(+ self.$n.estimate_heap_size())+)
347            }
348        }
349    };
350}
351
352#[cfg(not(feature = "const-generics"))]
353macro_rules! array_heap_size {
354    ($($n:tt)+) => {
355        $(
356        impl<T> DataSize for [T; $n]
357        where
358            T: DataSize,
359        {
360            const IS_DYNAMIC: bool = T::IS_DYNAMIC;
361
362            const STATIC_HEAP_SIZE: usize = T::STATIC_HEAP_SIZE * $n;
363
364            #[inline]
365            fn estimate_heap_size(&self) -> usize {
366                if T::IS_DYNAMIC {
367                    (&self[..]).iter().map(DataSize::estimate_heap_size).sum()
368                } else {
369                    T::STATIC_HEAP_SIZE * $n
370                }
371            }
372        }
373        )*
374    };
375}
376
377// Primitives
378non_dynamic_const_heap_size!(() u8 u16 u32 u64 u128 usize i8 i16 i32 i64 i128 isize bool char f32 f64, 0);
379
380// Assorted heapless `core` types
381non_dynamic_const_heap_size!(core::time::Duration, 0);
382
383tuple_heap_size!(0 T0; 1 T1);
384tuple_heap_size!(0 T0; 1 T1; 2 T2);
385tuple_heap_size!(0 T0; 1 T1; 2 T2; 3 T3);
386tuple_heap_size!(0 T0; 1 T1; 2 T2; 3 T3; 4 T4);
387tuple_heap_size!(0 T0; 1 T1; 2 T2; 3 T3; 4 T4; 5 T5);
388tuple_heap_size!(0 T0; 1 T1; 2 T2; 3 T3; 4 T4; 5 T5; 6 T6);
389tuple_heap_size!(0 T0; 1 T1; 2 T2; 3 T3; 4 T4; 5 T5; 6 T6; 7 T7);
390tuple_heap_size!(0 T0; 1 T1; 2 T2; 3 T3; 4 T4; 5 T5; 6 T6; 7 T7; 8 T8);
391tuple_heap_size!(0 T0; 1 T1; 2 T2; 3 T3; 4 T4; 5 T5; 6 T6; 7 T7; 8 T8; 9 T9);
392tuple_heap_size!(0 T0; 1 T1; 2 T2; 3 T3; 4 T4; 5 T5; 6 T6; 7 T7; 8 T8; 9 T9; 10 T10);
393tuple_heap_size!(0 T0; 1 T1; 2 T2; 3 T3; 4 T4; 5 T5; 6 T6; 7 T7; 8 T8; 9 T9; 10 T10; 11 T11);
394tuple_heap_size!(0 T0; 1 T1; 2 T2; 3 T3; 4 T4; 5 T5; 6 T6; 7 T7; 8 T8; 9 T9; 10 T10; 11 T11; 12 T12);
395tuple_heap_size!(0 T0; 1 T1; 2 T2; 3 T3; 4 T4; 5 T5; 6 T6; 7 T7; 8 T8; 9 T9; 10 T10; 11 T11; 12 T12; 13 T13);
396tuple_heap_size!(0 T0; 1 T1; 2 T2; 3 T3; 4 T4; 5 T5; 6 T6; 7 T7; 8 T8; 9 T9; 10 T10; 11 T11; 12 T12; 13 T13; 14 T14);
397tuple_heap_size!(0 T0; 1 T1; 2 T2; 3 T3; 4 T4; 5 T5; 6 T6; 7 T7; 8 T8; 9 T9; 10 T10; 11 T11; 12 T12; 13 T13; 14 T14; 15 T15);
398
399impl<T0> DataSize for (T0,)
400where
401    T0: DataSize,
402{
403    const IS_DYNAMIC: bool = T0::IS_DYNAMIC;
404    const STATIC_HEAP_SIZE: usize = T0::STATIC_HEAP_SIZE;
405
406    #[inline]
407    fn estimate_heap_size(&self) -> usize {
408        self.0.estimate_heap_size()
409    }
410}
411
412#[cfg(not(feature = "const-generics"))]
413array_heap_size!(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 128 192 256 384 512 1024 2048 4096 8192 16384 1048576 2097152 3145728 4194304);
414
415#[cfg(feature = "const-generics")]
416impl<T, const N: usize> DataSize for [T; N]
417where
418    T: DataSize,
419{
420    const IS_DYNAMIC: bool = T::IS_DYNAMIC;
421
422    const STATIC_HEAP_SIZE: usize = T::STATIC_HEAP_SIZE * N;
423
424    #[inline]
425    fn estimate_heap_size(&self) -> usize {
426        if T::IS_DYNAMIC {
427            self[..].iter().map(DataSize::estimate_heap_size).sum()
428        } else {
429            T::STATIC_HEAP_SIZE * N
430        }
431    }
432}
433
434// REFERENCES
435
436impl<T> DataSize for &T {
437    const IS_DYNAMIC: bool = false;
438
439    const STATIC_HEAP_SIZE: usize = 0;
440
441    #[inline]
442    fn estimate_heap_size(&self) -> usize {
443        0
444    }
445}
446
447impl<T> DataSize for &mut T {
448    const IS_DYNAMIC: bool = false;
449
450    const STATIC_HEAP_SIZE: usize = 0;
451
452    #[inline]
453    fn estimate_heap_size(&self) -> usize {
454        0
455    }
456}
457
458// COMMONLY USED NON-PRIMITIVE TYPES
459
460impl<T> DataSize for Option<T>
461where
462    T: DataSize,
463{
464    // Options are only not dynamic if their type has no heap data at all and is not dynamic.
465    const IS_DYNAMIC: bool = (T::IS_DYNAMIC || T::STATIC_HEAP_SIZE > 0);
466
467    const STATIC_HEAP_SIZE: usize = 0;
468
469    #[inline]
470    fn estimate_heap_size(&self) -> usize {
471        match self {
472            Some(val) => data_size(val),
473            None => 0,
474        }
475    }
476}
477
478impl<T, E> DataSize for Result<T, E>
479where
480    T: DataSize,
481    E: DataSize,
482{
483    // Results are only not dynamic if their types have no heap data at all and are not dynamic.
484    const IS_DYNAMIC: bool =
485        (T::IS_DYNAMIC || E::IS_DYNAMIC || (T::STATIC_HEAP_SIZE != E::STATIC_HEAP_SIZE));
486
487    const STATIC_HEAP_SIZE: usize = min(T::STATIC_HEAP_SIZE, E::STATIC_HEAP_SIZE);
488
489    #[inline]
490    fn estimate_heap_size(&self) -> usize {
491        match self {
492            Ok(val) => data_size(val),
493            Err(err) => data_size(err),
494        }
495    }
496}
497
498impl<T> DataSize for core::marker::PhantomData<T> {
499    const IS_DYNAMIC: bool = false;
500    const STATIC_HEAP_SIZE: usize = 0;
501
502    #[inline]
503    fn estimate_heap_size(&self) -> usize {
504        0
505    }
506}
507
508impl<T: DataSize> DataSize for core::ops::Range<T> {
509    const IS_DYNAMIC: bool = T::IS_DYNAMIC;
510    const STATIC_HEAP_SIZE: usize = 2 * T::STATIC_HEAP_SIZE;
511
512    #[inline]
513    fn estimate_heap_size(&self) -> usize {
514        self.start.estimate_heap_size() + self.end.estimate_heap_size()
515    }
516}
517
518#[cfg(test)]
519mod tests {
520    use crate as datasize; // Required for the derive macro.
521    use crate::{data_size, DataSize};
522
523    #[test]
524    fn test_for_simple_builtin_types() {
525        // We only sample some, as they are all macro generated.
526        assert_eq!(1u8.estimate_heap_size(), 0);
527        assert_eq!(1u16.estimate_heap_size(), 0);
528    }
529
530    #[test]
531    fn test_newtype_struct() {
532        #[derive(DataSize)]
533        struct Foo(u32);
534
535        assert!(!Foo::IS_DYNAMIC);
536        assert_eq!(Foo::STATIC_HEAP_SIZE, 0);
537        assert_eq!(data_size(&Foo(123)), 0);
538    }
539
540    #[test]
541    fn test_tuple_struct() {
542        #[derive(DataSize)]
543        struct Foo(u32, u8);
544
545        assert!(!Foo::IS_DYNAMIC);
546        assert_eq!(Foo::STATIC_HEAP_SIZE, 0);
547        assert_eq!(data_size(&Foo(123, 45)), 0);
548    }
549
550    #[test]
551    fn test_tuple_with_one_element() {
552        type Foo = (u32,);
553        assert!(!Foo::IS_DYNAMIC);
554        assert_eq!(Foo::STATIC_HEAP_SIZE, 0);
555
556        let foo: Foo = (456,);
557        assert_eq!(data_size(&foo), 0);
558    }
559
560    #[test]
561    fn test_result() {
562        assert_eq!(Result::<u8, u8>::STATIC_HEAP_SIZE, 0);
563        assert!(!Result::<u8, u8>::IS_DYNAMIC);
564
565        assert_eq!(Result::<u8, u16>::STATIC_HEAP_SIZE, 0);
566        assert!(!Result::<u8, u16>::IS_DYNAMIC);
567
568        #[cfg(feature = "std")]
569        assert_eq!(Result::<u8, Box<u16>>::STATIC_HEAP_SIZE, 0);
570        #[cfg(feature = "std")]
571        assert!(Result::<u8, Box<u16>>::IS_DYNAMIC);
572
573        #[cfg(feature = "std")]
574        assert_eq!(Result::<Box<u8>, u16>::STATIC_HEAP_SIZE, 0);
575        #[cfg(feature = "std")]
576        assert!(Result::<Box<u8>, u16>::IS_DYNAMIC);
577
578        #[cfg(feature = "std")]
579        assert_eq!(Result::<Box<u8>, Box<u16>>::STATIC_HEAP_SIZE, 1);
580        #[cfg(feature = "std")]
581        assert!(Result::<Box<u8>, Box<u16>>::IS_DYNAMIC);
582
583        #[cfg(feature = "std")]
584        assert_eq!(Result::<Box<u16>, Box<u16>>::STATIC_HEAP_SIZE, 2);
585        #[cfg(feature = "std")]
586        assert!(!Result::<Box<u16>, Box<u16>>::IS_DYNAMIC);
587
588        #[cfg(feature = "std")]
589        assert_eq!(Result::<u16, Vec<u16>>::STATIC_HEAP_SIZE, 0);
590        #[cfg(feature = "std")]
591        assert!(Result::<u16, Vec<u16>>::IS_DYNAMIC);
592
593        #[cfg(feature = "std")]
594        assert_eq!(Result::<Vec<u16>, u16>::STATIC_HEAP_SIZE, 0);
595        #[cfg(feature = "std")]
596        assert!(Result::<Vec<u16>, u16>::IS_DYNAMIC);
597
598        #[cfg(feature = "std")]
599        assert_eq!(Result::<Vec<u16>, Vec<u16>>::STATIC_HEAP_SIZE, 0);
600        #[cfg(feature = "std")]
601        assert!(Result::<Vec<u16>, Vec<u16>>::IS_DYNAMIC);
602    }
603
604    #[test]
605    fn test_empty_struct() {
606        #[derive(DataSize)]
607        struct Foo {}
608
609        #[derive(DataSize)]
610        struct Bar;
611
612        assert!(!Foo::IS_DYNAMIC);
613        assert!(!Bar::IS_DYNAMIC);
614
615        assert_eq!(Foo::STATIC_HEAP_SIZE, 0);
616        assert_eq!(Bar::STATIC_HEAP_SIZE, 0);
617
618        assert_eq!(data_size(&Foo {}), 0);
619        assert_eq!(data_size(&Bar), 0);
620    }
621
622    #[test]
623    fn test_empty_enum() {
624        #[derive(DataSize)]
625        enum Foo {}
626
627        assert!(!Foo::IS_DYNAMIC);
628        assert_eq!(Foo::STATIC_HEAP_SIZE, 0);
629
630        // We cannot instantiate empty enums.
631    }
632
633    #[test]
634    fn macro_does_not_panic_on_foreign_attributes() {
635        #[derive(DataSize)]
636        /// This docstring shows up as `#[doc = ""]`...
637        struct Foo {
638            /// This docstring shows up as `#[doc = ""]`...
639            dummy: u8,
640        }
641    }
642
643    // TODO: This does not work, the equivalent should be constructed using `trybuild`.
644    // #[test]
645    // #[should_panic = "unexpected datasize attribute"]
646    // fn macro_panics_on_invalid_data_size_attribute() {
647    //     #[derive(DataSize)]
648    //     /// This docstring shows up as `#[doc = ""]`...
649    //     struct Foo {
650    //         #[data_size(invalid)]
651    //         /// This docstring shows up as `#[doc = ""]`...
652    //         dummy: u8,
653    //     }
654    // }
655
656    #[test]
657    fn keeps_where_clauses_on_structs() {
658        #[allow(dead_code)]
659        #[derive(DataSize)]
660        struct Foo<T>
661        where
662            T: Copy,
663        {
664            field: T,
665        }
666    }
667
668    #[test]
669    fn keeps_where_clauses_on_enums() {
670        #[allow(dead_code)]
671        #[derive(DataSize)]
672        enum Foo<T>
673        where
674            T: Copy,
675        {
676            Value(T),
677        }
678    }
679
680    #[test]
681    fn use_with_annotation() {
682        fn ds_for_field_b(value: &u32) -> usize {
683            assert_eq!(*value, 2); // set in the example
684            1234
685        }
686
687        #[derive(DataSize)]
688        struct Foo {
689            field_a: u32,
690            #[data_size(with = ds_for_field_b)]
691            field_b: u32,
692            field_c: u32,
693        }
694
695        let value = Foo {
696            field_a: 1,
697            field_b: 2,
698            field_c: 3,
699        };
700        assert_eq!(value.estimate_heap_size(), 1234);
701    }
702}