get-size2 0.11.0

Determine the size in bytes an object occupies inside RAM.
Documentation
Determine the size in bytes an object occupies inside RAM.

[`size_of`](core::mem::size_of) already reports how many bytes a value occupies on the stack. Applications like caches or memory budgets also need to know how many bytes a value owns on the *heap*, which is what the [`GetSize`] trait adds.

```toml
get-size2 = { version = "^0.10", features = ["derive"] }
```

# Quick start

```rust
use get_size2::GetSize;

let value = String::from("Hello World!");

// The 12 bytes of the string data live on the heap.
assert_eq!(value.get_heap_size(), 12);
assert_eq!(value.get_size(), String::get_stack_size() + 12);

// Only the allocated capacity is accounted for, not the length.
let mut buffer = Vec::with_capacity(1024);
buffer.push(1u8);
assert_eq!(buffer.get_heap_size(), 1024);
```

The trait provides three methods:

| Method | Returns |
| ------ | ------- |
| [`get_stack_size`]GetSize::get_stack_size | The bytes occupied on the stack, by default [`size_of::<Self>()`]core::mem::size_of |
| [`get_heap_size`]GetSize::get_heap_size | The bytes this value owns on the heap |
| [`get_size`]GetSize::get_size | The sum of both |

It is implemented for the primitives and for most types of [`core`], [`alloc`](https://doc.rust-lang.org/alloc/) and [`std`], for your own types via [the derive macro](#deriving-getsize), and for a number of popular third party types behind [feature flags](#feature-flags).

# Ownership based accounting

Only bytes *owned* by a value are accounted for. Borrowed bytes belong to their owner, so anything reached through a reference or a raw pointer counts as zero:

```rust
use get_size2::GetSize;

#[derive(GetSize)]
struct Borrowing<'a> {
    value: &'a String,
}

let value = String::from("hello");
assert_eq!(value.get_heap_size(), 5);

// The struct only borrows the string, so its heap bytes are not counted.
let borrowing = Borrowing { value: &value };
assert_eq!(borrowing.get_heap_size(), 0);
```

Beware of auto-dereferencing when calling the method on a reference yourself, since the compiler will resolve it to the referenced value:

```rust
use get_size2::GetSize;

let value = String::from("hello");

assert_eq!((&value).get_heap_size(), 5); // Rewritten to `value.get_heap_size()`!
assert_eq!(GetSize::get_heap_size(&&value), 0); // Fully qualified syntax, no deref.
assert_eq!(get_size2::heap_size(&&value), 0); // Or use the free function.
```

The derive macro always uses fully qualified syntax, so derived implementations are not affected.

Shared ownership is the one exception to the borrowing rule: an `Rc` or `Arc` genuinely owns its allocation, so its contents *are* counted, including the stack bytes of the value it points to.

```rust
use get_size2::GetSize;
use std::sync::Arc;

let value = Arc::new(String::from("hello"));
assert_eq!(value.get_heap_size(), String::get_stack_size() + 5);
```

# Tracking shared ownership

Because shared ownership is counted, a structure holding several handles to the *same* allocation would account for it more than once. A [`GetSizeTracker`] prevents that by remembering which addresses it has already seen:

```rust
use get_size2::{GetSize, StandardTracker};
use std::sync::Arc;

let shared = Arc::new(String::from("hello"));
let pair = (Arc::clone(&shared), Arc::clone(&shared));

let single = String::get_stack_size() + 5;

// Without a tracker, both handles are accounted for.
assert_eq!(pair.get_heap_size(), 2 * single);

// With a tracker, the shared allocation is only counted once.
let (size, _tracker) = pair.get_heap_size_with_tracker(StandardTracker::new());
assert_eq!(size, single);
```

Derived implementations track by default: the [`get_heap_size`](GetSize::get_heap_size) generated by the derive macro starts a fresh [`default_tracker`] and passes it through all fields.

```rust
use get_size2::GetSize;
use std::sync::Arc;

#[derive(GetSize)]
struct Shared {
    first: Arc<String>,
    second: Arc<String>,
}

let shared = Arc::new(String::from("hello"));
let value = Shared {
    first: Arc::clone(&shared),
    second: Arc::clone(&shared),
};

assert_eq!(value.get_heap_size(), String::get_stack_size() + 5);
```

Three trackers ship with this crate:

| Tracker | Behavior |
| ------- | -------- |
| [`StandardTracker`] | Remembers every address it has seen. Requires the `alloc` feature. |
| [`NoTracker`] | Tracks nothing and always returns the same answer. Used by the untracked methods. |
| [`DefaultTracker`] | Alias for whichever of the two the derive macro can use, see [`default_tracker`]. |

[`GetSizeTracker`] is also implemented for `&mut T`, `Box<T>`, `Mutex<T>`, `RwLock<T>`, `Arc<Mutex<T>>` and `Arc<RwLock<T>>` of any tracker, so a single tracker can be reused across calls or shared between threads. Implement the trait yourself if you need different bookkeeping.

# Deriving `GetSize`

Enable the `derive` feature and add `#[derive(GetSize)]` to your struct or enum. The generated implementation sums up the heap size of every field:

```rust
use get_size2::GetSize;

#[derive(GetSize)]
struct Data {
    name: String,
    id: u64,
}

let data = Data { name: "Adam".into(), id: 1 };
assert_eq!(data.get_heap_size(), 4);
```

Fields whose type does not implement [`GetSize`] can be ignored, given a fixed size, or measured by a helper function using the `#[get_size(...)]` attribute. See the [`get-size-derive2` documentation](https://docs.rs/get-size-derive2) for the full attribute reference. Unions are not supported.

# Implementing `GetSize` manually

Only [`get_heap_size_with_tracker`](GetSize::get_heap_size_with_tracker) needs to be implemented; the other methods have suitable default implementations. The default returns `0`, which is already correct for any type that does not own heap memory.

```rust
use get_size2::{GetSize, GetSizeTracker};

struct Buffer {
    data: Vec<u8>,
    len: usize,
}

impl GetSize for Buffer {
    fn get_heap_size_with_tracker<T: GetSizeTracker>(&self, tracker: T) -> (usize, T) {
        // Pass the tracker on so shared ownership deeper in the structure stays deduplicated.
        self.data.get_heap_size_with_tracker(tracker)
    }
}

let buffer = Buffer { data: Vec::with_capacity(64), len: 0 };
assert_eq!(buffer.get_heap_size(), 64);
```

# Feature flags

## `no_std` support

This crate is `#![no_std]`. Which standard library implementations are available is controlled by two features, both enabled by the `std` default feature:

| Feature | Implemented types |
| ------- | ----------------- |
| _(none)_ | [`core`] types, like the primitives, [`Option`], [`Result`], ranges, tuples, arrays, references and raw pointers |
| `alloc` | [`alloc`]https://doc.rust-lang.org/alloc/ types, like [`Vec`]alloc::vec::Vec, [`String`]alloc::string::String, [`Box`]alloc::boxed::Box, `Rc` and `Arc` |
| `std` | Types only provided by [`std`], like [`HashMap`]std::collections::HashMap, [`PathBuf`]std::path::PathBuf or `Mutex` |

To build for a `no_std` target, disable the default features:

```toml
get-size2 = { version = "^0.10", default-features = false, features = ["alloc", "derive"] }
```

Without `alloc` there is nothing to allocate the set of seen addresses with, so [`StandardTracker`] is unavailable and [`default_tracker`] falls back to [`NoTracker`]. That is sufficient, because there is no shared ownership type to track in the first place.

## Integrations

Each of these features implements [`GetSize`] for the types of the respective crate:

| Feature | Types | Needs `std` |
| ------- | ----- | ----------- |
| `bytes` | `Bytes`, `BytesMut` | |
| `chrono` | `NaiveDate`, `NaiveTime`, `NaiveDateTime`, `DateTime`, `TimeDelta`, ... | |
| `chrono-tz` | `TzOffset` | |
| `compact-str` | `CompactString` | |
| `dashmap` | `DashMap`, `DashSet` | yes |
| `half` | `f16`, `bf16` | |
| `hashbrown` | `HashMap`, `HashSet`, `HashTable` | |
| `indexmap` | `IndexMap`, `IndexSet` | |
| `ordermap` | `OrderMap`, `OrderSet` | |
| `orx-concurrent-vec` | `ConcurrentVec` | |
| `parking_lot` | `Mutex`, `RwLock`, also as trackers | yes |
| `portable-atomic` | All `portable_atomic` atomics, including the 128 bit ones | |
| `roaring` | `RoaringBitmap`, `RoaringTreemap` | |
| `smallvec` | `SmallVec` | |
| `thin-vec` | `ThinVec` | |
| `url` | `Url` | |

Features marked as needing `std` enable it implicitly. `all-features-no-std` is a convenience pack activating everything which works without `std`, that is every feature except `dashmap` and `parking_lot`.

# Accuracy

The reported sizes are a best effort based on the public API of the measured types:

- Unused capacity is counted, since it is allocated. Small string and small vector optimizations are respected, so inline data is not double counted.
- Allocator bookkeeping, padding between allocations and the internal node overhead of tree and hash based collections are not counted, so results are a lower bound on what the allocator actually reserved.
- Values behind a raw pointer are not counted, since ownership cannot be determined.
- A locking type ([`Mutex`]std::sync::Mutex, [`RwLock`]std::sync::RwLock, `RefCell`) is measured by reading its content. A poisoned lock is measured anyway, while an already mutably borrowed `RefCell` is reported as `0` instead of panicking.