A Box like type that keeps small objects on the stack and can allocate bigger objects as
Box, Rc or Arc. DerefMut is copy-on-write when the Smox is backed by Rc or Arc
Rationale
In generic contexts one often does not know how big the objects will be. If they are small
enough
they can be kept on the stack for better performance. If they are bigger they need to be
allocated on the heap. Smox allows to do this in a transparent way.
Example
# #![cfg_attr(feature = "nightly", allow(unused))]
# #[cfg(not(feature = "nightly"))]
# fn main() {
use smox::*;
#[derive(Clone)]
struct Generic<T: Clone> {
value: SmoxRc<T, 16>,
}
impl<T: Clone> Generic<T> {
fn new(value: T) -> Self {
Self {
value: SmoxRc::new_rc(value),
}
}
}
assert!(std::mem::size_of::<Generic<i32>>() <= 16);
assert!(std::mem::size_of::<Generic<[i32; 100]>>() <= 16);
let mut small = Generic::new(42i32);
*small.value += 1;
assert_eq!(*small.value, 43);
let big = Generic::new([0i32;100]);
let mut big_clone = big.clone();
big_clone.value[0] = 1;
assert_eq!(big.value[0], 0);
assert_eq!(big_clone.value[0], 1);
# }
# #[cfg(feature = "nightly")]
# fn main() {}
Details
- In the current implementation, the given
SIZE is also the size of a Smox or
the size of a box types whatever is bigger.
Current stable rust lacks the generic_const_exprs support to constrain the size when
heap storage is used to a single pointer. This will change in the future, perhaps with a
nightly feature flag.
- The biggest spaces saving are when you can benefit from the copy-on-write behavior of
Rc or
Arc. Use either of these when there is a chance that the objects will be cloned.
- When your
Smox should stay as small as possible then try the MINIMAL_SIZE as size limit.
Nightly Support
With the nightly feature enabled, Smox uses generic_const_exprs to optimize away unused
in-place storage. When T is larger than SIZE, the in-place storage becomes 0 bytes instead
of wasting SIZE bytes. Because of alignement requirements it will still be sized a multiple
of a pointer size. When stored on heap it is a single pointer, when stored in_place it is
size_of::<T>().next_multiple_of(std::mem::size_of::<usize>().
However, this optimization requires adding where bounds when using Smox with generic type
parameters:
# #![cfg_attr(feature = "nightly", feature(generic_const_exprs))]
# #![cfg_attr(not(feature = "nightly"), allow(unused))]
# #[cfg(feature = "nightly")]
# fn main() {
use smox::*;
#[derive(Clone)]
struct Generic<T: Clone>
where
[(); optimal_size::<T, 32>()]:,
{
value: SmoxRc<T, 32>,
}
impl<T: Clone> Generic<T>
where
[(); optimal_size::<T, 32>()]:,
{
fn new(value: T) -> Self {
Self {
value: SmoxRc::new_rc(value),
}
}
}
assert_eq!(std::mem::size_of::<Generic<i32>>(), std::mem::size_of::<usize>());
assert_eq!(std::mem::size_of::<Generic<[i32; 100]>>(), std::mem::size_of::<usize>());
assert_eq!(
std::mem::size_of::<Generic<[u8; 12]>>(),
std::mem::size_of::<[u8; 12]>().next_multiple_of(std::mem::size_of::<usize>())
);
let mut small = Generic::new(42i32);
*small.value += 1;
assert_eq!(*small.value, 43);
let big = Generic::new([0i32;100]);
let mut big_clone = big.clone();
big_clone.value[0] = 1;
assert_eq!(big.value[0], 0);
assert_eq!(big_clone.value[0], 1);
# }
# #[cfg(not(feature = "nightly"))]
# fn main() {}