diff --git a/Cargo.toml b/Cargo.toml index 7d76736..276d6c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "get-size" description = "Determine the size in bytes an object occupies inside RAM." -version = "0.1.3" +version = "0.1.4" edition = "2021" authors = ["Denis Kerp"] readme = "README.md" @@ -11,7 +11,7 @@ keywords = ["size", "heap", "ram", "cache", "memory"] categories = ["memory-management", "caching"] [dependencies] -get-size-derive = { version = "^0.1.2", optional = true } +get-size-derive = { version = "^0.1", optional = true } [features] default = [] diff --git a/README.md b/README.md index aa2a674..21b519f 100644 --- a/README.md +++ b/README.md @@ -8,15 +8,38 @@ Determine the size in bytes an object occupies inside RAM. The [`GetSize`] trait can be used to determine the size of an object inside the stack as well as in the heap. The [`size_of`](https://doc.rust-lang.org/std/mem/fn.size_of.html) function provided by the standard library can already be used to determine the size of an object in the stack, but many application (e.g. for caching) do also need to know the number of bytes occupied inside the heap, for which this library provides an appropriate trait. +##### Example + +We use [`GetSize`] to determine number of bytes occupied by both a `String` and a `Vec` of bytes. Note that the `Vec` has already allocated a capacity of `1024` bytes, and does thus correctly show a heap size of `1024`, even if only `1` byte is currently in use. + +```rust +use get_size::GetSize; + +fn main() { + let value = String::from("Hello World!"); + + assert_eq!(String::get_stack_size(), std::mem::size_of::()); + assert_eq!(value.get_heap_size(), 12); + + assert_eq!(value.get_size(), std::mem::size_of::() + 12); + + + let mut buffer = Vec::with_capacity(1024); // 1KB allocated on the heap. + buffer.push(1u8); // 1 byte in use. + + assert_eq!(buffer.len(), 1); + assert_eq!(buffer.get_heap_size(), 1024); +} +``` + ## Ownership based accounting This library follows the idea that only bytes owned by a certain object should be accounted for, and not bytes owned by different objects which are only borrowed. This means in particular that objects referenced by pointers are ignored. -### Example +##### Example ```rust use get_size::GetSize; -use get_size_derive::*; #[derive(GetSize)] struct Test<'a> { @@ -49,9 +72,9 @@ fn main() { } ``` -On the other hand references implemented as shared ownership are treated as owned values. It is your responsibility to ensure that the bytes occupied by them are not counted twice in your application! +On the other hand references implemented as shared ownership are treated as owned values. It is your responsibility to ensure that the bytes occupied by them are not counted twice in your application. The `ignore` attribute might be helpful, [see below](#ignoring-certain-values). -### Example +##### Example ```rust use std::sync::Arc; @@ -81,9 +104,11 @@ You will need to activate the `derive` feature first, which is disabled by defau get-size = { version = "^0.1", features = ["derive"] } ``` -Then you can easily derive [`GetSize`]. If you want the derive macro to ignore a certain struct field you can add the `ignore` attribute to it. This might be usefull if some values do not implement the [`GetSize`] trait and do not have data on the heap, or if the data on the heap has already been accounted for somewhere else. +Note that the derive macro _does not support unions_. You have to manually implement it for them. + +### Examples -### Example +Deriving [`GetSize`] for a struct: ```rust use get_size::GetSize; @@ -92,29 +117,19 @@ use get_size::GetSize; pub struct OwnStruct { value1: String, value2: u64, - #[get_size(ignore)] - value3: ExternalStruct, -} - -// Does not implement GetSize, so we ignore it. -pub struct ExternalStruct { - value1: u8, } fn main() { let test = OwnStruct { value1: "Hello".into(), value2: 123, - value3: ExternalStruct{ value1: 255 }, }; assert_eq!(test.get_heap_size(), 5); } ``` -As already mentioned you can also derive [`GetSize`] for enums. - -### Example +Deriving [`GetSize`] for an enum: ```rust use get_size::GetSize; @@ -152,49 +167,229 @@ fn main() { } ``` -You can also derive [`GetSize`] on structs and enums with generics. In that case the generated trait implementation will require all generic types to also implement [`GetSize`]. - -This behavior may be unfavourable if one or more generic types are ignored duo to the corresponding struct field being ignored. In that case you can also use the `ignore` attribute at the struct level to specifiy the generic parameters which shall not be required to implement [`GetSize`] themselves. - -### Example +The derive macro does also work with generics. The generated trait implementation will by default require all generic types to implement [`GetSize`] themselves, but this [can be changed](#ignoring-certain-generic-types). ```rust use get_size::GetSize; -use get_size_derive::*; #[derive(GetSize)] -#[get_size(ignore(B, C))] -struct TestStructGenericsIgnore { +struct TestStructGenerics { value1: A, - #[get_size(ignore)] value2: B, - #[get_size(ignore)] - value3: C, } -// Does not implement GetSize, so we ignore it. -struct TestStructNoGetSize { - value: String, +#[derive(GetSize)] +enum TestEnumGenerics { + Variant1(A), + Variant2(B), } fn main() { - let no_impl = TestStructNoGetSize { - value: "World!".into(), - }; - - // If we did not ignore the C type, the following would not implement - // GetSize since C does not implement it. - let test: TestStructGenericsIgnore = TestStructGenericsIgnore { + let test: TestStructGenerics = TestStructGenerics { value1: "Hello".into(), value2: 123, - value3: no_impl, }; assert_eq!(test.get_heap_size(), 5); + + let test = String::from("Hello"); + let test: TestEnumGenerics = TestEnumGenerics::Variant1(test); + + assert_eq!(test.get_heap_size(), 5); + + let test: TestEnumGenerics = TestEnumGenerics::Variant2(100); + + assert_eq!(test.get_heap_size(), 0); +} +``` + +### Dealing with external types which do not implement GetSize + +Deriving [`GetSize`] is straight forward if all the types contained in your data structure implement [`GetSize`] themselves, but this might not always be the case. For that reason the derive macro offers some helpers to assist you in that case. + +Note that the helpers are currently only available for regular structs, that is they do neither support tuple structs nor enums. + +#### Ignoring certain values + +You can tell the derive macro to ignore certain struct fields by adding the `ignore` attribute to them. The generated implementation of [`get_heap_size`] will then simple skip this field. + +##### Example + +The idiomatic use case for this helper is if you use shared ownership and do not want your data to be counted twice. + +```rust +use std::sync::Arc; +use get_size::GetSize; + +#[derive(GetSize)] +struct PrimaryStore { + id: u64, + shared_data: Arc>, +} + +#[derive(GetSize)] +struct SecondaryStore { + id: u64, + #[get_size(ignore)] + shared_data: Arc>, +} + +fn main() { + let shared_data = Arc::new(Vec::with_capacity(1024)); + + let primary_data = PrimaryStore { + id: 1, + shared_data: Arc::clone(&shared_data), + }; + + let secondary_data = SecondaryStore { + id: 2, + shared_data, + }; + + // Note that Arc does also store the Vec's stack data on the heap. + assert_eq!(primary_data.get_heap_size(), Vec::::get_stack_size() + 1024); + assert_eq!(secondary_data.get_heap_size(), 0); +} +``` + +##### Example + +But you may also use this as a band aid, if a certain struct fields type does not implement [`GetSize`]. + +Be aware though that this will result in an implementation which will return incorrect results, unless the heap size of that type is indeed always zero and can thus be ignored. It is therefor advisable to use one of the next two helper options instead. + +```rust +use get_size::GetSize; + +// Does not implement GetSize! +struct TestStructNoGetSize { + value: String, +} + +// Implements GetSize, even through one field's type does not implement it. +#[derive(GetSize)] +struct TestStruct { + name: String, + #[get_size(ignore)] + ignored_value: TestStructNoGetSize, +} + +fn main() { + let ignored_value = TestStructNoGetSize { + value: "Hello world!".into(), + }; + + let test = TestStruct { + name: "Adam".into(), + ignored_value, + }; + + // Note that the result is lower then it should be. + assert_eq!(test.get_heap_size(), 4); +} +``` + +#### Returning a fixed value + +In same cases you may be dealing with external types which allocate a fixed amount of bytes at the heap. In this case you may use the `size` attribute to always account the given field with a fixed value. + +```rust +use get_size::GetSize; + +#[derive(GetSize)] +struct TestStruct { + id: u64, + #[get_size(size = 1024)] + buffer: Buffer1024, // Always allocates exactly 1KB at the heap. +} + +fn main() { + let test = TestStruct { + id: 1, + buffer: Buffer1024::new(), + }; + + assert_eq!(test.get_heap_size(), 1024); +} +``` + +#### Using a helper function + +In same cases you may be dealing with an external data structure for which you know how to calculate its heap size using its public methods. In that case you may either use the newtype pattern to implement [`GetSize`] for it directly, or you can use the `size_fn` attribute, which will call the given function in order to calculate the fields heap size. + +The latter is especially usefull if you can make use of a certain trait to calculate the heap size for multiple types. + +Note that unlike in other crates, the name of the function to be called is __not__ encapsulated by double-quotes ("), but rather given directly. + +```rust +use get_size::GetSize; + +#[derive(GetSize)] +struct TestStruct { + id: u64, + #[get_size(size_fn = vec_alike_helper)] + buffer: ExternalVecAlike, +} + +// NOTE: We assume that slice.len()==slice.capacity() +fn vec_alike_helper(slice: &V) -> usize +where + V: AsRef<[T]>, +{ + std::mem::size_of::() * slice.as_ref().len() +} + +fn main() { + let buffer = vec![0u8; 512]; + let buffer: ExternalVecAlike = buffer.into(); + + let test = TestStruct { + id: 1, + buffer, + }; + + assert_eq!(test.get_heap_size(), 512); } ``` -Note that the derive macro does not support unions. You have to manually implement it for them. +#### Ignoring certain generic types + +If your struct uses generics, but the fields at which they are stored are ignored or get handled by helpers because the generic does not implement [`GetSize`], you will have to mark these generics with a special struct level `ignore` attribute. Otherwise the derived [`GetSize`] implementation would still require these generics to implement [`GetSize`], even through there is no need for it. + +```rust +use get_size::GetSize; + +#[derive(GetSize)] +#[get_size(ignore(B, C, D))] +struct TestStructHelpers { + value1: A, + #[get_size(size = 100)] + value2: B, + #[get_size(size_fn = get_size_helper)] + value3: C, + #[get_size(ignore)] + value4: D, +} + +// Does not implement GetSize +struct NoGS {} + +fn get_size_helper(_value: &C) -> usize { + 50 +} + +fn main() { + let test: TestStructHelpers = TestStructHelpers { + value1: "Hello".into(), + value2: NoGS {}, + value3: NoGS {}, + value4: 123, + }; + + assert_eq!(test.get_heap_size(), 5 + 100 + 50); +} +``` ## License diff --git a/get-size-derive/Cargo.toml b/get-size-derive/Cargo.toml index 8b12553..a4c1c42 100644 --- a/get-size-derive/Cargo.toml +++ b/get-size-derive/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "get-size-derive" description = "Derives the GetSize trait." -version = "0.1.2" +version = "0.1.3" edition = "2021" authors = ["Denis Kerp"] readme = "README.md" @@ -14,8 +14,9 @@ categories = ["memory-management", "caching"] proc-macro = true [dependencies] -syn = { version = "^1", features = ["derive"] } +syn = { version = "^2", features = ["derive", "parsing"] } quote = "^1" +attribute-derive = "^0.6" [dev-dependencies] -get-size = "^0.1" +get-size = { path = "../", features = ["derive"] } diff --git a/get-size-derive/README.md b/get-size-derive/README.md index c80a97a..bb8dd34 100644 --- a/get-size-derive/README.md +++ b/get-size-derive/README.md @@ -6,72 +6,298 @@ Derives [`GetSize`] for structs and enums. -The derive macro will provide a costum implementation of the [`get_heap_size`] method, which will simply call [`get_heap_size`] on all contained values and add the values up. This implies that all values contained in the struct or enum most implement the [`GetSize`] trait themselves. +The derive macro will provide a costum implementation of the [`get_heap_size`] method, which will simply call [`get_heap_size`] on all contained values and add the values up. This implies that by default all values contained in the struct or enum most implement the [`GetSize`] trait themselves. -When deriving [`GetSize`] for structs you can use the `ignore` attribute to make the derive macro ignore certain values. This might be usefull if some values do not implement the [`GetSize`] trait and do not have data on the heap, or if the data on the heap has already been accounted for somewhere else. +Note that the derive macro _does not support unions_. You have to manually implement it for them. -## Example +### Examples + +Deriving [`GetSize`] for a struct: ```rust +use get_size::GetSize; + #[derive(GetSize)] pub struct OwnStruct { value1: String, value2: u64, - #[get_size(ignore)] - value3: ExternalStruct, } -// Does not implement GetSize, so we ignore it. -pub struct ExternalStruct { - value1: u8, +fn main() { + let test = OwnStruct { + value1: "Hello".into(), + value2: 123, + }; + + assert_eq!(test.get_heap_size(), 5); } ``` -You can also derive [`GetSize`] on structs and enums with generics. In that case the generated trait implementation will require all generic types to also implement [`GetSize`]. +Deriving [`GetSize`] for an enum: + +```rust +use get_size::GetSize; + +#[derive(GetSize)] +pub enum TestEnum { + Variant1(u8, u16, u32), + Variant2(String), + Variant3, + Variant4{x: String, y: String}, +} + +#[derive(GetSize)] +pub enum TestEnumNumber { + Zero = 0, + One = 1, + Two = 2, +} -This behavior may be unfavourable if one or more generic types are ignored duo to the corresponding struct field being ignored. In that case you can also use the `ignore` attribute at the struct level to specifiy the generic parameters which shall not be required to implement [`GetSize`] themselves. +fn main() { + let test = TestEnum::Variant1(1, 2, 3); + assert_eq!(test.get_heap_size(), 0); + + let test = TestEnum::Variant2("Hello".into()); + assert_eq!(test.get_heap_size(), 5); -## Example + let test = TestEnum::Variant3; + assert_eq!(test.get_heap_size(), 0); + + let test = TestEnum::Variant4{x: "Hello".into(), y: "world".into()}; + assert_eq!(test.get_heap_size(), 5 + 5); + + let test = TestEnumNumber::One; + assert_eq!(test.get_heap_size(), 0); +} +``` + +The derive macro does also work with generics. The generated trait implementation will by default require all generic types to implement [`GetSize`] themselves, but this [can be changed](#ignoring-certain-generic-types). ```rust use get_size::GetSize; -use get_size_derive::*; #[derive(GetSize)] -#[get_size(ignore(B, C))] -struct TestStructGenericsIgnore { +struct TestStructGenerics { value1: A, - #[get_size(ignore)] value2: B, - #[get_size(ignore)] - value3: C, } -// Does not implement GetSize, so we ignore it. +#[derive(GetSize)] +enum TestEnumGenerics { + Variant1(A), + Variant2(B), +} + +fn main() { + let test: TestStructGenerics = TestStructGenerics { + value1: "Hello".into(), + value2: 123, + }; + + assert_eq!(test.get_heap_size(), 5); + + let test = String::from("Hello"); + let test: TestEnumGenerics = TestEnumGenerics::Variant1(test); + + assert_eq!(test.get_heap_size(), 5); + + let test: TestEnumGenerics = TestEnumGenerics::Variant2(100); + + assert_eq!(test.get_heap_size(), 0); +} +``` + +### Dealing with external types which do not implement GetSize + +Deriving [`GetSize`] is straight forward if all the types contained in your data structure implement [`GetSize`] themselves, but this might not always be the case. For that reason the derive macro offers some helpers to assist you in that case. + +Note that the helpers are currently only available for regular structs, that is they do neither support tuple structs nor enums. + +#### Ignoring certain values + +You can tell the derive macro to ignore certain struct fields by adding the `ignore` attribute to them. The generated implementation of [`get_heap_size`] will then simple skip this field. + +##### Example + +The idiomatic use case for this helper is if you use shared ownership and do not want your data to be counted twice. + +```rust +use std::sync::Arc; +use get_size::GetSize; + +#[derive(GetSize)] +struct PrimaryStore { + id: u64, + shared_data: Arc>, +} + +#[derive(GetSize)] +struct SecondaryStore { + id: u64, + #[get_size(ignore)] + shared_data: Arc>, +} + +fn main() { + let shared_data = Arc::new(Vec::with_capacity(1024)); + + let primary_data = PrimaryStore { + id: 1, + shared_data: Arc::clone(&shared_data), + }; + + let secondary_data = SecondaryStore { + id: 2, + shared_data, + }; + + // Note that Arc does also store the Vec's stack data on the heap. + assert_eq!(primary_data.get_heap_size(), Vec::::get_stack_size() + 1024); + assert_eq!(secondary_data.get_heap_size(), 0); +} +``` + +##### Example + +But you may also use this as a band aid, if a certain struct fields type does not implement [`GetSize`]. + +Be aware though that this will result in an implementation which will return incorrect results, unless the heap size of that type is indeed always zero and can thus be ignored. It is therefor advisable to use one of the next two helper options instead. + +```rust +use get_size::GetSize; + +// Does not implement GetSize! struct TestStructNoGetSize { value: String, } +// Implements GetSize, even through one field's type does not implement it. +#[derive(GetSize)] +struct TestStruct { + name: String, + #[get_size(ignore)] + ignored_value: TestStructNoGetSize, +} + fn main() { - let no_impl = TestStructNoGetSize { - value: "World!".into(), - }; + let ignored_value = TestStructNoGetSize { + value: "Hello world!".into(), + }; + + let test = TestStruct { + name: "Adam".into(), + ignored_value, + }; + + // Note that the result is lower then it should be. + assert_eq!(test.get_heap_size(), 4); +} +``` + +#### Returning a fixed value + +In same cases you may be dealing with external types which allocate a fixed amount of bytes at the heap. In this case you may use the `size` attribute to always account the given field with a fixed value. + +```rust +use get_size::GetSize; + +#[derive(GetSize)] +struct TestStruct { + id: u64, + #[get_size(size = 1024)] + buffer: Buffer1024, // Always allocates exactly 1KB at the heap. +} + +fn main() { + let test = TestStruct { + id: 1, + buffer: Buffer1024::new(), + }; + + assert_eq!(test.get_heap_size(), 1024); +} +``` + +#### Using a helper function + +In same cases you may be dealing with an external data structure for which you know how to calculate its heap size using its public methods. In that case you may either use the newtype pattern to implement [`GetSize`] for it directly, or you can use the `size_fn` attribute, which will call the given function in order to calculate the fields heap size. + +The latter is especially usefull if you can make use of a certain trait to calculate the heap size for multiple types. - // If we did not ignore the C type, the following would not implement - // GetSize since C does not implement it. - let test: TestStructGenericsIgnore = TestStructGenericsIgnore { +Note that unlike in other crates, the name of the function to be called is __not__ encapsulated by double-quotes ("), but rather given directly. + +```rust +use get_size::GetSize; + +#[derive(GetSize)] +struct TestStruct { + id: u64, + #[get_size(size_fn = vec_alike_helper)] + buffer: ExternalVecAlike, +} + +// NOTE: We assume that slice.len()==slice.capacity() +fn vec_alike_helper(slice: &V) -> usize +where + V: AsRef<[T]>, +{ + std::mem::size_of::() * slice.as_ref().len() +} + +fn main() { + let buffer = vec![0u8; 512]; + let buffer: ExternalVecAlike = buffer.into(); + + let test = TestStruct { + id: 1, + buffer, + }; + + assert_eq!(test.get_heap_size(), 512); +} +``` + +#### Ignoring certain generic types + +If your struct uses generics, but the fields at which they are stored are ignored or get handled by helpers because the generic does not implement [`GetSize`], you will have to mark these generics with a special struct level `ignore` attribute. Otherwise the derived [`GetSize`] implementation would still require these generics to implement [`GetSize`], even through there is no need for it. + +```rust +use get_size::GetSize; + +#[derive(GetSize)] +#[get_size(ignore(B, C, D))] +struct TestStructHelpers { + value1: A, + #[get_size(size = 100)] + value2: B, + #[get_size(size_fn = get_size_helper)] + value3: C, + #[get_size(ignore)] + value4: D, +} + +// Does not implement GetSize +struct NoGS {} + +fn get_size_helper(_value: &C) -> usize { + 50 +} + +fn main() { + let test: TestStructHelpers = TestStructHelpers { value1: "Hello".into(), - value2: 123, - value3: no_impl, + value2: NoGS {}, + value3: NoGS {}, + value4: 123, }; - assert_eq!(test.get_heap_size(), 5); + assert_eq!(test.get_heap_size(), 5 + 100 + 50); } ``` ## Panics -The derive macro will panic if used on unions since these are currently not supported. This might change in the future. +The derive macro will panic if used on unions since these are currently not supported. Note that there will be a compilation error if one of the (not ignored) values encountered does not implement the [`GetSize`] trait. diff --git a/get-size-derive/src/lib.md b/get-size-derive/src/lib.md index 1326bd9..2dcb02a 100644 --- a/get-size-derive/src/lib.md +++ b/get-size-derive/src/lib.md @@ -1,74 +1,307 @@ Derives [`GetSize`] for structs and enums. -The derive macro will provide a costum implementation of the [`get_heap_size`] method, which will simply call [`get_heap_size`] on all contained values and add the values up. This implies that all values contained in the struct or enum must implement the [`GetSize`] trait themselves. +The derive macro will provide a costum implementation of the [`get_heap_size`] method, which will simply call [`get_heap_size`] on all contained values and add the values up. This implies that by default all values contained in the struct or enum must implement the [`GetSize`] trait themselves. -When deriving [`GetSize`] for structs you can use the `ignore` attribute to make the derive macro ignore certain values. This might be usefull if some values do not implement the [`GetSize`] trait and do not have data on the heap, or if the data on the heap has already been accounted for somewhere else. +Note that the derive macro _does not support unions_. You have to manually implement it for them. -# Example +### Examples + +Deriving [`GetSize`] for a struct: + +```rust +use get_size::GetSize; -```no_run -# use get_size::GetSize; -# use get_size_derive::*; -# #[derive(GetSize)] pub struct OwnStruct { value1: String, value2: u64, - #[get_size(ignore)] - value3: ExternalStruct, } -// Does not implement GetSize, so we ignore it. -pub struct ExternalStruct { - value1: u8, +fn main() { + let test = OwnStruct { + value1: "Hello".into(), + value2: 123, + }; + + assert_eq!(test.get_heap_size(), 5); } ``` -You can also derive [`GetSize`] on structs and enums with generics. In that case the generated trait implementation will require all generic types to also implement [`GetSize`]. +Deriving [`GetSize`] for an enum: + +```rust +use get_size::GetSize; -This behavior may be unfavourable if one or more generic types are ignored duo to the corresponding struct field being ignored. In that case you can also use the `ignore` attribute at the struct level to specifiy the generic parameters which shall not be required to implement [`GetSize`] themselves. +#[derive(GetSize)] +pub enum TestEnum { + Variant1(u8, u16, u32), + Variant2(String), + Variant3, + Variant4{x: String, y: String}, +} -# Example +#[derive(GetSize)] +pub enum TestEnumNumber { + Zero = 0, + One = 1, + Two = 2, +} +fn main() { + let test = TestEnum::Variant1(1, 2, 3); + assert_eq!(test.get_heap_size(), 0); + + let test = TestEnum::Variant2("Hello".into()); + assert_eq!(test.get_heap_size(), 5); + + let test = TestEnum::Variant3; + assert_eq!(test.get_heap_size(), 0); + + let test = TestEnum::Variant4{x: "Hello".into(), y: "world".into()}; + assert_eq!(test.get_heap_size(), 5 + 5); + + let test = TestEnumNumber::One; + assert_eq!(test.get_heap_size(), 0); +} ``` + +The derive macro does also work with generics. The generated trait implementation will by default require all generic types to implement [`GetSize`] themselves, but this [can be changed](#ignoring-certain-generic-types). + +```rust use get_size::GetSize; -use get_size_derive::*; #[derive(GetSize)] -#[get_size(ignore(B, C))] -struct TestStructGenericsIgnore { +struct TestStructGenerics { value1: A, - #[get_size(ignore)] value2: B, - #[get_size(ignore)] - value3: C, } -// Does not implement GetSize, so we ignore it. +#[derive(GetSize)] +enum TestEnumGenerics { + Variant1(A), + Variant2(B), +} + +fn main() { + let test: TestStructGenerics = TestStructGenerics { + value1: "Hello".into(), + value2: 123, + }; + + assert_eq!(test.get_heap_size(), 5); + + let test = String::from("Hello"); + let test: TestEnumGenerics = TestEnumGenerics::Variant1(test); + + assert_eq!(test.get_heap_size(), 5); + + let test: TestEnumGenerics = TestEnumGenerics::Variant2(100); + + assert_eq!(test.get_heap_size(), 0); +} +``` + +## Dealing with external types which do not implement GetSize + +Deriving [`GetSize`] is straight forward if all the types contained in your data structure implement [`GetSize`] themselves, but this might not always be the case. For that reason the derive macro offers some helpers to assist you in that case. + +Note that the helpers are currently only available for regular structs, that is they do neither support tuple structs nor enums. + +### Ignoring certain values + +You can tell the derive macro to ignore certain struct fields by adding the `ignore` attribute to them. The generated implementation of [`get_heap_size`] will then simple skip this field. + +#### Example + +The idiomatic use case for this helper is if you use shared ownership and do not want your data to be counted twice. + +```rust +use std::sync::Arc; +use get_size::GetSize; + +#[derive(GetSize)] +struct PrimaryStore { + id: u64, + shared_data: Arc>, +} + +#[derive(GetSize)] +struct SecondaryStore { + id: u64, + #[get_size(ignore)] + shared_data: Arc>, +} + +fn main() { + let shared_data = Arc::new(Vec::with_capacity(1024)); + + let primary_data = PrimaryStore { + id: 1, + shared_data: Arc::clone(&shared_data), + }; + + let secondary_data = SecondaryStore { + id: 2, + shared_data, + }; + + // Note that Arc does also store the Vec's stack data on the heap. + assert_eq!(primary_data.get_heap_size(), Vec::::get_stack_size() + 1024); + assert_eq!(secondary_data.get_heap_size(), 0); +} +``` + +#### Example + +But you may also use this as a band aid, if a certain struct fields type does not implement [`GetSize`]. + +Be aware though that this will result in an implementation which will return incorrect results, unless the heap size of that type is indeed always zero and can thus be ignored. It is therefor advisable to use one of the next two helper options instead. + +```rust +use get_size::GetSize; + +// Does not implement GetSize! struct TestStructNoGetSize { value: String, } +// Implements GetSize, even through one field's type does not implement it. +#[derive(GetSize)] +struct TestStruct { + name: String, + #[get_size(ignore)] + ignored_value: TestStructNoGetSize, +} + fn main() { - let no_impl = TestStructNoGetSize { - value: "World!".into(), - }; + let ignored_value = TestStructNoGetSize { + value: "Hello world!".into(), + }; + + let test = TestStruct { + name: "Adam".into(), + ignored_value, + }; + + // Note that the result is lower then it should be. + assert_eq!(test.get_heap_size(), 4); +} +``` + +### Returning a fixed value + +In same cases you may be dealing with external types which allocate a fixed amount of bytes at the heap. In this case you may use the `size` attribute to always account the given field with a fixed value. + +```rust +use get_size::GetSize; +# +# struct Buffer1024 {} +# +# impl Buffer1024 { +# fn new() -> Self { +# Self {} +# } +# } + +#[derive(GetSize)] +struct TestStruct { + id: u64, + #[get_size(size = 1024)] + buffer: Buffer1024, // Always allocates exactly 1KB at the heap. +} + +fn main() { + let test = TestStruct { + id: 1, + buffer: Buffer1024::new(), + }; + + assert_eq!(test.get_heap_size(), 1024); +} +``` + +### Using a helper function + +In same cases you may be dealing with an external data structure for which you know how to calculate its heap size using its public methods. In that case you may either use the newtype pattern to implement [`GetSize`] for it directly, or you can use the `size_fn` attribute, which will call the given function in order to calculate the fields heap size. + +The latter is especially usefull if you can make use of a certain trait to calculate the heap size for multiple types. - // If we did not ignore the C type, the following would not implement - // GetSize since C does not implement it. - let test: TestStructGenericsIgnore = TestStructGenericsIgnore { +Note that unlike in other crates, the name of the function to be called is __not__ encapsulated by double-quotes ("), but rather given directly. + +```rust +use get_size::GetSize; +# +# type ExternalVecAlike = Vec; + +#[derive(GetSize)] +struct TestStruct { + id: u64, + #[get_size(size_fn = vec_alike_helper)] + buffer: ExternalVecAlike, +} + +// NOTE: We assume that slice.len()==slice.capacity() +fn vec_alike_helper(slice: &V) -> usize +where + V: AsRef<[T]>, +{ + std::mem::size_of::() * slice.as_ref().len() +} + +fn main() { + let buffer = vec![0u8; 512]; + let buffer: ExternalVecAlike = buffer.into(); + + let test = TestStruct { + id: 1, + buffer, + }; + + assert_eq!(test.get_heap_size(), 512); +} +``` + +### Ignoring certain generic types + +If your struct uses generics, but the fields at which they are stored are ignored or get handled by helpers because the generic does not implement [`GetSize`], you will have to mark these generics with a special struct level `ignore` attribute. Otherwise the derived [`GetSize`] implementation would still require these generics to implement [`GetSize`], even through there is no need for it. + +```rust +use get_size::GetSize; + +#[derive(GetSize)] +#[get_size(ignore(B, C, D))] +struct TestStructHelpers { + value1: A, + #[get_size(size = 100)] + value2: B, + #[get_size(size_fn = get_size_helper)] + value3: C, + #[get_size(ignore)] + value4: D, +} + +// Does not implement GetSize +struct NoGS {} + +fn get_size_helper(_value: &C) -> usize { + 50 +} + +fn main() { + let test: TestStructHelpers = TestStructHelpers { value1: "Hello".into(), - value2: 123, - value3: no_impl, + value2: NoGS {}, + value3: NoGS {}, + value4: 123, }; - assert_eq!(test.get_heap_size(), 5); + assert_eq!(test.get_heap_size(), 5 + 100 + 50); } ``` # Panics -The derive macro will panic if used on unions since these are currently not supported. This might change in the future. +The derive macro will panic if used on unions since these are currently not supported. Note that there will be a compilation error if one of the (not ignored) values encountered does not implement the [`GetSize`] trait. diff --git a/get-size-derive/src/lib.rs b/get-size-derive/src/lib.rs index 8eb9e77..32da0cd 100644 --- a/get-size-derive/src/lib.rs +++ b/get-size-derive/src/lib.rs @@ -4,43 +4,24 @@ use proc_macro::TokenStream; use quote::quote; use syn; +use attribute_derive::Attribute; -fn has_nested_flag_attribute(attr: &syn::Attribute, name: &'static str, flag: &'static str) -> bool { - if let Ok(meta) = attr.parse_meta() { - if let Some(ident) = meta.path().get_ident() { - if &ident.to_string()==name { - if let syn::Meta::List(list) = meta { - for nested in list.nested.iter() { - if let syn::NestedMeta::Meta(nmeta) = nested { - if let syn::Meta::Path(path) = nmeta { - let path = path.get_ident().expect("Invalid attribute syntax! (no ident)").to_string(); - if &path==flag { - return true - } - } - } - } - } - } - } - } - - false +#[derive(Attribute, Default, Debug)] +#[attribute(ident = get_size)] +struct StructFieldAttribute { + #[attribute(conflicts = [size_fn, ignore])] + size: Option, + #[attribute(conflicts = [size, ignore])] + size_fn: Option, + #[attribute(conflicts = [size, size_fn])] + ignore: bool, } -fn has_nested_flag_attribute_list(list: &Vec, name: &'static str, flag: &'static str) -> bool { - for attr in list.iter() { - if has_nested_flag_attribute(attr, name, flag) { - return true; - } - } - false -} -fn extract_ignored_generics_list(list: &Vec) -> Vec { +fn extract_ignored_generics_list(list: &Vec) -> Vec { let mut collection = Vec::new(); for attr in list.iter() { @@ -52,38 +33,35 @@ fn extract_ignored_generics_list(list: &Vec) -> Vec { collection } -fn extract_ignored_generics(attr: &syn::Attribute) -> Vec { +fn extract_ignored_generics(attr: &syn::Attribute) -> Vec { let mut collection = Vec::new(); - if let Ok(meta) = attr.parse_meta() { - if let Some(ident) = meta.path().get_ident() { - if &ident.to_string()!="get_size" { - return collection; - } - if let syn::Meta::List(list) = meta { - for nested in list.nested.iter() { - if let syn::NestedMeta::Meta(nmeta) = nested { - let ident = nmeta.path().get_ident().expect("Invalid attribute syntax! (no iden)"); - if &ident.to_string()!="ignore" { - panic!("Invalid attribute syntax! Unknown name {:?}", ident.to_string()); - } + // Skip all attributes which do not belong to us. + if !attr.meta.path().is_ident("get_size") { + return collection; + } - if let syn::Meta::List(list) = nmeta { - for nested in list.nested.iter() { - if let syn::NestedMeta::Meta(nmeta) = nested { - if let syn::Meta::Path(path) = nmeta { - let path = path.get_ident().expect("Invalid attribute syntax! (no ident)").to_string(); - collection.push(path); - } - } - } - } + // Make sure it is a list. + let list = attr.meta.require_list().unwrap(); - } - } - } + // Parse the nested meta. + // #[get_size(ignore(A, B))] + list.parse_nested_meta(|meta| { + // We only parse the ignore attributes. + if !meta.path.is_ident("ignore") { + return Ok(()); // Just skip. } - } + + meta.parse_nested_meta(|meta| { + for segment in meta.path.segments { + collection.push(segment); + } + + Ok(()) + })?; + + Ok(()) + }).unwrap(); collection } @@ -91,21 +69,22 @@ fn extract_ignored_generics(attr: &syn::Attribute) -> Vec { // Add a bound `T: GetSize` to every type parameter T, unless we ignore it. fn add_trait_bounds( mut generics: syn::Generics, - ignored: &Vec, + ignored: &Vec, ) -> syn::Generics { for param in &mut generics.params { if let syn::GenericParam::Type(type_param) = param { - let name = type_param.ident.to_string(); let mut found = false; for ignored in ignored.iter() { - if ignored==&name { + if ignored.ident==type_param.ident { found = true; break; } } + if found { continue; } + type_param.bounds.push(syn::parse_quote!(GetSize)); } } @@ -244,8 +223,25 @@ pub fn derive_get_size(input: TokenStream) -> TokenStream { for field in data_struct.fields.iter() { - // Check if the value should be ignored. If so skip it. - if has_nested_flag_attribute_list(&field.attrs, "get_size", "ignore") { + // Parse all relevant attributes. + let attr = StructFieldAttribute::from_attributes(&field.attrs).unwrap(); + + // NOTE There will be no attributes if this is a tuple struct. + if let Some(size) = attr.size { + cmds.push(quote! { + total += #size; + }); + + continue; + } else if let Some(size_fn) = attr.size_fn { + let ident = field.ident.as_ref().unwrap(); + + cmds.push(quote! { + total += #size_fn(&self.#ident); + }); + + continue; + } else if attr.ignore { continue; } diff --git a/src/lib.md b/src/lib.md index 9eedf26..ca91df4 100644 --- a/src/lib.md +++ b/src/lib.md @@ -2,15 +2,38 @@ Determine the size in bytes an object occupies inside RAM. The [`GetSize`] trait can be used to determine the size of an object inside the stack as well as in the heap. The [`size_of`](std::mem::size_of) function provided by the standard library can already be used to determine the size of an object in the stack, but many application (e.g. for caching) do also need to know the number of bytes occupied inside the heap, for which this library provides an appropriate trait. +#### Example + +We use [`GetSize`] to determine number of bytes occupied by both a [`String`] and a [`Vec`] of bytes. Note that the [`Vec`] has already allocated a capacity of `1024` bytes, and does thus correctly show a heap size of `1024`, even if only `1` byte is currently in use. + +```rust +use get_size::GetSize; + +fn main() { + let value = String::from("Hello World!"); + + assert_eq!(String::get_stack_size(), std::mem::size_of::()); + assert_eq!(value.get_heap_size(), 12); + + assert_eq!(value.get_size(), std::mem::size_of::() + 12); + + + let mut buffer = Vec::with_capacity(1024); // 1KB allocated on the heap. + buffer.push(1u8); // 1 byte in use. + + assert_eq!(buffer.len(), 1); + assert_eq!(buffer.get_heap_size(), 1024); +} +``` + # Ownership based accounting This library follows the idea that only bytes owned by a certain object should be accounted for, and not bytes owned by different objects which are only borrowed. This means in particular that objects referenced by pointers are ignored. -## Example +#### Example ```rust use get_size::GetSize; -use get_size_derive::*; #[derive(GetSize)] struct Test<'a> { @@ -43,9 +66,9 @@ fn main() { } ``` -On the other hand references implemented as shared ownership are treated as owned values. It is your responsibility to ensure that the bytes occupied by them are not counted twice in your application! +On the other hand references implemented as shared ownership are treated as owned values. It is your responsibility to ensure that the bytes occupied by them are not counted twice in your application. The `ignore` attribute might be helpful, [see below](#ignoring-certain-values). -## Example +#### Example ```rust use std::sync::Arc; @@ -67,7 +90,7 @@ fn main() { The [`GetSize`] trait is already implemented for most objects defined by the standard library, like [`Vec`](std::vec::Vec), [`HashMap`](std::collections::HashMap), [`String`] as well as all the primitive values, like [`u8`], [`i32`] etc. -Unless you have a complex datastructure which requires a manual implementation, you can easily derive [`GetSize`] for your own structs and enums. The derived implementation will implement [`GetSize::get_heap_size`] by simply calling [`GetSize::get_heap_size`] on all values contained inside the struct or enum variant and return the sum of them. +Unless you have a complex data structure which requires a manual implementation, you can easily derive [`GetSize`] for your own structs and enums. The derived implementation will implement [`GetSize::get_heap_size`] by simply calling [`GetSize::get_heap_size`] on all values contained inside the struct or enum variant and return the sum of them. You will need to activate the `derive` feature first, which is disabled by default. Add the following to your `cargo.toml`: @@ -75,9 +98,11 @@ You will need to activate the `derive` feature first, which is disabled by defau get-size = { version = "^0.1", features = ["derive"] } ``` -Then you can easily derive [`GetSize`]. If you want the derive macro to ignore a certain struct field you can add the `ignore` attribute to it. This might be usefull if some values do not implement the [`GetSize`] trait and do not have data on the heap, or if the data on the heap has already been accounted for somewhere else. +Note that the derive macro _does not support unions_. You have to manually implement it for them. + +### Examples -## Example +Deriving [`GetSize`] for a struct: ```rust use get_size::GetSize; @@ -86,29 +111,19 @@ use get_size::GetSize; pub struct OwnStruct { value1: String, value2: u64, - #[get_size(ignore)] - value3: ExternalStruct, -} - -// Does not implement GetSize, so we ignore it. -pub struct ExternalStruct { - value1: u8, } fn main() { let test = OwnStruct { value1: "Hello".into(), value2: 123, - value3: ExternalStruct{ value1: 255 }, }; assert_eq!(test.get_heap_size(), 5); } ``` -As already mentioned you can also derive [`GetSize`] for enums. - -## Example +Deriving [`GetSize`] for an enum: ```rust use get_size::GetSize; @@ -146,46 +161,236 @@ fn main() { } ``` -You can also derive [`GetSize`] on structs and enums with generics. In that case the generated trait implementation will require all generic types to also implement [`GetSize`]. - -This behavior may be unfavourable if one or more generic types are ignored duo to the corresponding struct field being ignored. In that case you can also use the `ignore` attribute at the struct level to specifiy the generic parameters which shall not be required to implement [`GetSize`] themselves. - -## Example +The derive macro does also work with generics. The generated trait implementation will by default require all generic types to implement [`GetSize`] themselves, but this [can be changed](#ignoring-certain-generic-types). ```rust use get_size::GetSize; -use get_size_derive::*; #[derive(GetSize)] -#[get_size(ignore(B, C))] -struct TestStructGenericsIgnore { +struct TestStructGenerics { value1: A, - #[get_size(ignore)] value2: B, - #[get_size(ignore)] - value3: C, } -// Does not implement GetSize, so we ignore it. -struct TestStructNoGetSize { - value: String, +#[derive(GetSize)] +enum TestEnumGenerics { + Variant1(A), + Variant2(B), } fn main() { - let no_impl = TestStructNoGetSize { - value: "World!".into(), - }; - - // If we did not ignore the C type, the following would not implement - // GetSize since C does not implement it. - let test: TestStructGenericsIgnore = TestStructGenericsIgnore { + let test: TestStructGenerics = TestStructGenerics { value1: "Hello".into(), value2: 123, - value3: no_impl, }; assert_eq!(test.get_heap_size(), 5); + + let test = String::from("Hello"); + let test: TestEnumGenerics = TestEnumGenerics::Variant1(test); + + assert_eq!(test.get_heap_size(), 5); + + let test: TestEnumGenerics = TestEnumGenerics::Variant2(100); + + assert_eq!(test.get_heap_size(), 0); +} +``` + +## Dealing with external types which do not implement GetSize + +Deriving [`GetSize`] is straight forward if all the types contained in your data structure implement [`GetSize`] themselves, but this might not always be the case. For that reason the derive macro offers some helpers to assist you in that case. + +Note that the helpers are currently only available for regular structs, that is they do neither support tuple structs nor enums. + +### Ignoring certain values + +You can tell the derive macro to ignore certain struct fields by adding the `ignore` attribute to them. The generated implementation of [`GetSize::get_heap_size`] will then simple skip this field. + +#### Example + +The idiomatic use case for this helper is if you use shared ownership and do not want your data to be counted twice. + +```rust +use std::sync::Arc; +use get_size::GetSize; + +#[derive(GetSize)] +struct PrimaryStore { + id: u64, + shared_data: Arc>, +} + +#[derive(GetSize)] +struct SecondaryStore { + id: u64, + #[get_size(ignore)] + shared_data: Arc>, +} + +fn main() { + let shared_data = Arc::new(Vec::with_capacity(1024)); + + let primary_data = PrimaryStore { + id: 1, + shared_data: Arc::clone(&shared_data), + }; + + let secondary_data = SecondaryStore { + id: 2, + shared_data, + }; + + // Note that Arc does also store the Vec's stack data on the heap. + assert_eq!(primary_data.get_heap_size(), Vec::::get_stack_size() + 1024); + assert_eq!(secondary_data.get_heap_size(), 0); +} +``` + +#### Example + +But you may also use this as a band aid, if a certain struct fields type does not implement [`GetSize`]. + +Be aware though that this will result in an implementation which will return incorrect results, unless the heap size of that type is indeed always zero and can thus be ignored. It is therefor advisable to use one of the next two helper options instead. + +```rust +use get_size::GetSize; + +// Does not implement GetSize! +struct TestStructNoGetSize { + value: String, +} + +// Implements GetSize, even through one field's type does not implement it. +#[derive(GetSize)] +struct TestStruct { + name: String, + #[get_size(ignore)] + ignored_value: TestStructNoGetSize, +} + +fn main() { + let ignored_value = TestStructNoGetSize { + value: "Hello world!".into(), + }; + + let test = TestStruct { + name: "Adam".into(), + ignored_value, + }; + + // Note that the result is lower then it should be. + assert_eq!(test.get_heap_size(), 4); +} +``` + +### Returning a fixed value + +In same cases you may be dealing with external types which allocate a fixed amount of bytes at the heap. In this case you may use the `size` attribute to always account the given field with a fixed value. + +```rust +use get_size::GetSize; +# +# struct Buffer1024 {} +# +# impl Buffer1024 { +# fn new() -> Self { +# Self {} +# } +# } + +#[derive(GetSize)] +struct TestStruct { + id: u64, + #[get_size(size = 1024)] + buffer: Buffer1024, // Always allocates exactly 1KB at the heap. +} + +fn main() { + let test = TestStruct { + id: 1, + buffer: Buffer1024::new(), + }; + + assert_eq!(test.get_heap_size(), 1024); +} +``` + +### Using a helper function + +In same cases you may be dealing with an external data structure for which you know how to calculate its heap size using its public methods. In that case you may either use the newtype pattern to implement [`GetSize`] for it directly, or you can use the `size_fn` attribute, which will call the given function in order to calculate the fields heap size. + +The latter is especially usefull if you can make use of a certain trait to calculate the heap size for multiple types. + +Note that unlike in other crates, the name of the function to be called is __not__ encapsulated by double-quotes ("), but rather given directly. + +```rust +use get_size::GetSize; +# +# type ExternalVecAlike = Vec; + +#[derive(GetSize)] +struct TestStruct { + id: u64, + #[get_size(size_fn = vec_alike_helper)] + buffer: ExternalVecAlike, +} + +// NOTE: We assume that slice.len()==slice.capacity() +fn vec_alike_helper(slice: &V) -> usize +where + V: AsRef<[T]>, +{ + std::mem::size_of::() * slice.as_ref().len() +} + +fn main() { + let buffer = vec![0u8; 512]; + let buffer: ExternalVecAlike = buffer.into(); + + let test = TestStruct { + id: 1, + buffer, + }; + + assert_eq!(test.get_heap_size(), 512); } ``` -Note that the derive macro does not support unions. You have to manually implement it for them. +### Ignoring certain generic types + +If your struct uses generics, but the fields at which they are stored are ignored or get handled by helpers because the generic does not implement [`GetSize`], you will have to mark these generics with a special struct level `ignore` attribute. Otherwise the derived [`GetSize`] implementation would still require these generics to implement [`GetSize`], even through there is no need for it. + +```rust +use get_size::GetSize; + +#[derive(GetSize)] +#[get_size(ignore(B, C, D))] +struct TestStructHelpers { + value1: A, + #[get_size(size = 100)] + value2: B, + #[get_size(size_fn = get_size_helper)] + value3: C, + #[get_size(ignore)] + value4: D, +} + +// Does not implement GetSize +struct NoGS {} + +fn get_size_helper(_value: &C) -> usize { + 50 +} + +fn main() { + let test: TestStructHelpers = TestStructHelpers { + value1: "Hello".into(), + value2: NoGS {}, + value3: NoGS {}, + value4: 123, + }; + + assert_eq!(test.get_heap_size(), 5 + 100 + 50); +} +``` diff --git a/src/lib.rs b/src/lib.rs index e220224..65c3057 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -147,7 +147,7 @@ impl GetSize for SystemTime {} impl<'a, T> GetSize for Cow<'a, T> where - T: ToOwned + GetSize, + T: ToOwned, ::Owned: GetSize, { fn get_heap_size(&self) -> usize { diff --git a/src/tests/mod.rs b/src/tests/mod.rs index f888a79..3da0abd 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -16,6 +16,7 @@ fn derive_struct() { assert_eq!(test.get_heap_size(), 5); } + #[derive(GetSize)] pub struct TestStructGenerics { value1: A, @@ -32,6 +33,7 @@ fn derive_struct_with_generics() { assert_eq!(test.get_heap_size(), 5); } + #[derive(GetSize)] #[get_size(ignore(B, C))] #[allow(dead_code)] @@ -63,6 +65,38 @@ fn derive_struct_with_generics_and_ignore() { assert_eq!(test.get_heap_size(), 5); } + +#[derive(GetSize)] +#[get_size(ignore(B, C))] +#[allow(dead_code)] +struct TestStructHelpers { + value1: A, + #[get_size(size = 100)] + value2: B, + #[get_size(size_fn = get_size_helper)] + value3: C, +} + +fn get_size_helper(_value: &C) -> usize { + 50 +} + +#[test] +fn derive_struct_with_generics_and_helpers() { + let no_impl = TestStructNoGetSize { + value: "World!".into(), + }; + + let test: TestStructHelpers = TestStructHelpers { + value1: "Hello".into(), + value2: 123, + value3: no_impl, + }; + + assert_eq!(test.get_heap_size(), 5 + 100 + 50); +} + + #[derive(GetSize)] pub struct TestStructGenericsLifetimes<'a, A, B> { value1: A, @@ -81,6 +115,7 @@ fn derive_struct_with_generics_and_lifetimes() { assert_eq!(test.get_heap_size(), 5); } + #[derive(GetSize)] pub enum TestEnum { Variant1(u8, u16, u32), @@ -125,6 +160,7 @@ fn derive_enum() { assert_eq!(test.get_heap_size(), 5 + 5); } + #[derive(GetSize)] pub enum TestEnumGenerics<'a, A, B, C> { Variant1(A), @@ -179,6 +215,7 @@ fn derive_enum_generics_issue1() { assert_eq!(test.get_heap_size(), 8*std::mem::size_of::>()); } + #[derive(GetSize)] pub enum TestEnum2 { Zero = 0, @@ -206,4 +243,4 @@ pub struct TestNewType(u64); fn derive_newtype() { let test = TestNewType(0); assert_eq!(u64::get_stack_size(), test.get_size()); -} \ No newline at end of file +}