| Crates.io | generic-container |
| lib.rs | generic-container |
| version | 0.2.2 |
| created_at | 2025-07-20 02:17:40.156409+00 |
| updated_at | 2025-08-16 17:45:12.754688+00 |
| description | Abstract over "containers" that hold a T, such as a T itself, Box |
| homepage | |
| repository | https://github.com/robofinch/generic-container |
| max_upload_size | |
| id | 1760680 |
| size | 81,700 |
Abstract over how a T is stored and accessed by using generic "containers", bounded by
the container traits here. A container owns a T and can provide references to it, or be
consumed to return the inner T (if T is Sized).
Some containers are fallible, or can only provide immutable references and not mutable references. The container traits are meant to be specific enough that a generic container can be bound more tightly by the interface it needs, to support more container implementations.
Skip to here for a list of the provided container implementations.
Someone who only needs to use your type from a single thread, will never clone it, and will only
use a single concrete type for a generic or dyn will likely have very different preferences
from someone who wants to use your type from multiple threads and instantiate a bulky generic
type with many combinations of generic parameters.
This concern is likely not even applicable for Clone + Send + Sync types that have no
generics, and in the other direction, the two use cases may simply be far too different to
cleanly unify.
But when "how will this T be stored?" is a major blocker against flexibly supporting both
sorts of uses in a performant way, this crate can help. The traits here allow a type
to abstract over how something is held and accessed by using a generic "container", and
blanket implementations for Trait can implement Trait for any container wrapping
dyn Trait, or effectively T: Trait in general with the help of a container wrapper type
1.
Choices like these can be deferred to the consumer of your types (or traits):
Send or Sync worth it?Copy or dupe::Dupe, or perhaps by the container implementing Clone
even when the contained type is not Clone)?dyn, or are some of the concrete types involved not known until
runtime? If so, a user may prefer or need a dyn trait object, and from the first two
bullets, maybe a user would need Rc<dyn Trait> rather than Box<dyn Trait>, and in general
may want something like Container<dyn Trait>2.Particular traits worth abstracting over are Clone (or a cheap clone like dupe::Dupe
in particular), Send, and Sync.
Additionally, trait authors may wish to allow any generics bound by your traits to optionally
use dyn instead of monomorphizing everything; a standard way to allow for this is to
implement YourTrait for Box<dyn YourTrait> and perhaps a few other smart pointers, but even
better is providing a blanket implementation for YourTrait for any
C: ?Sized + Container<dyn YourTrait> 2.
use generic_container::FragileContainer;
trait MyTrait {
fn calculate_something(&self) -> u32;
}
// Whenever something is generic over `MyTrait`, thanks to this blanket impl, we could opt into
// using `Box<dyn MyTrait>`, `Arc<dyn MyTrait>`, and so on. This way, being generic over
// `MyTrait` gives the user a strict superset of the abilities they'd have if we only used
// `dyn MyTrait` internally.
impl<C: ?Sized + FragileContainer<dyn MyTrait>> MyTrait for C {
// You should warn users like this when depending on `FragileContainer`.
// Note that someone solely using the `MyTrait` interface could not encounter this problem:
// any `FragileContainer<dyn MyTrait>` is a perfect drop-in for a `MyTrait`.
//
/// Calculate something using the internal `MyTrait` implementation.
///
/// # Fragility: Potential Panics or Deadlocks
/// If `C` does not also implement `Container` and there is an existing borrow from `C`
/// at the time `calculate_something` is called, a panic or deadlock may occur.
///
/// See [`FragileContainer::get_ref`].
#[inline]
fn calculate_something(&self) -> u32 {
self.get_ref().calculate_something()
}
}
struct NeedsToCalculateSomething<T: MyTrait>(T);
// This is effectively the same thing you'd get if you used a `dyn` object instead of a generic
// above; the user of your struct loses nothing.
type NeedsToCalculateSomethingDyn = NeedsToCalculateSomething<Box<dyn MyTrait>>;
Eight main container traits are provided here, with every combination of the following aspects:
try_get_ref or try_get_mut can fail, and
get_ref or get_mut cannot be provided.try_get_ref (or similar) is called by a thread that already has a live reference
to the value stored in the container.Mutability is indicated by the prefix Mut, fallibility by the prefix Try, and fragility
by the prefix Fragile. They are combined in the order "FragileTryMutContainer".
There are also two de facto trait aliases which replace "FragileTry" with "Base" in the two
FragileTry*Container traits.
Except for the Base*Container traits intended as aliases, implementors of the traits
must manually implement each of them; there are no blanket [Container] implementations for
TryContainer types whose error types are uninhabited, for instance.
When bounding a generic by a container trait, you should generally bound by the minimum
container interface you need, plus any other marker trait needed, like Clone (or a cheap
clone like dupe::Dupe), Send, and Sync. Conversely, you should bound the T which
the container is required to store as tightly as you can, especially by Sized, Send,
and Sync.
The Ref associated types of some container implementations (and RefMut for mutable
containers) may have nontrivial Drop impls that interfere with other attempts to borrow from
the container, and possibly from other clones of the container referencing the same inner data.
Such a container might be "fragile", unless it implements additional container traits
indicating that it is not.
Being "fragile" means that a single thread should not attempt to get multiple live references
to the T in the container, whether from the same container instance or clones referencing the
same inner T. Doing so risks a panic or deadlock (such as in the case of Arc<Mutex<T>>).
In other words, the fragile container traits do not guarantee that the container handles
reentrancy gracefully. A thread should drop any borrow obtained from the fragile container's
methods accessing its inner T before a new reference to the inner T can be obtained without
any risk of panic or deadlock.
Common examples of containers include T itself, Box<T>, Rc<T>, Rc<RefCell<T>>, Arc<T>,
Arc<RwLock<T>>, and Arc<Mutex<T>>.
Additionally, container traits are implemented for [CheckedRcRefCell<T>] (from this crate) and
Arc<ThreadCheckedMutex<T>> (when the thread-checked-lock feature is enabled).
Note that CheckedRcRefCell<T> and Arc<ThreadCheckedMutex<T>> essentially shift how the
runtime invariants of a RefCell or Mutex are enforced; with a fragile implementation, the
user is required to enforce them (on pain of panics or deadlocks), while with a fallible
implementation, such usage will still fail, but not fatally. Defaulting to the fragile
implementations is likely the best choice.
Some container implementations choose to panic if a poison error is encountered, as a poison error can only occur if another thread has already panicked.
Other crates may implement container traits for their own types.
This crate provides the following container implementations:
For MutContainer<T> (and its supertraits):
T itselfBox<T>For Container<T> (and its supertraits):
Rc<T>Arc<T>For FragileMutContainer<T> (and its supertraits):
Rc<RefCell<T>>Arc<RwLock<T>> (implementation may panic on poison)Arc<Mutex<T>> (implementation may panic on poison)For TryMutContainer<T> (and its supertraits):
CheckedRcRefCell<T>Arc<ThreadCheckedMutex<T>> (only if the thread-checked-lock feature is enabled)Currently, Rust doesn't allow bounds like
where C: for<T: Send> Container<T> + Clone + Send + Sync.
The solution is to define an extra trait with whatever you need as the bounds of a GAT
(generic associated type):
use generic_container::FragileMutContainer;
use dupe::Dupe;
pub trait NeededContainerStuff {
// Implementors should use `T: ?Sized` when possible. But right now, the only way to create,
// for example, `Arc<Mutex<[T]>>` is via unsizing coercion from `Arc<Mutex<[T; N]>>`;
// as such, a `T: ?Sized` bound would be somewhat useless without also requiring the
// container to support unsizing coercion, which currently requires nightly-only traits.
type MutableContainer<T: Send>: FragileMutContainer<T> + Dupe + Send + Sync;
}
If some data needs thread-safe mutability, but you don't want to pay the cost of a lock for read-only data, you can use multiple GATs:
use generic_container::{FragileMutContainer, Container};
use dupe::Dupe;
pub trait NeededContainerStuff {
// E.g.: `Arc<Mutex<T>>`, or something `parking_lot`-based
type MutableContainer<T: Send>: FragileMutContainer<T> + Dupe + Send + Sync;
// E.g.: `Arc<T>`
type ReadOnlyContainer<T: Send + Sync>: Container<T> + Dupe + Send + Sync;
}
Such a trait is called a "container kind trait" (with implementations being "container kinds", just
as implementations of the container traits are "containers"). Relevant characteristics for a
container kind's container include the eight container traits, Send + Sync bounds (possibly only
when T is Send + Sync, or just Send), Dupe, whether the GAT allows T to be unsized, and
Debug bounds.
Not every conceivably-useful container kind trait is provided, as there would be exponentially-many such traits. Note also that creating simple container kind traits that can be combined into more complicated bounds does work, but not well. A container kind should set the GAT of each container kind trait it implements to the same container type; this can be asserted or required with Rust's type system, but the trait solver doesn't understand it very well. Such container kind traits would likely not be pleasant to use.
When the kinds feature is enabled, container kinds and container kind traits for common sorts of
containers are provided. They do not use Dupe bounds, and do not mess with type-equality
shenanigans that confuse the trait solver.
std: enables support for Arc<Mutex<T>> and Arc<RwLock<T>>. Enabled by default. Implies
the alloc feature.alloc: enables container implementations based on Box, Rc, Arc, and RefCell, including
CheckedRcRefCell. Without alloc, the container traits and GenericContainer are still
available, and T is a container for itself. Enabled by default.kinds: provides several container kinds and container kind traits (see above).thread-checked-lock: if enabled, TryMutContainer<T> is implemented for
Arc<ThreadCheckedMutex<T>>. Implies the std feature.serde: derives Serialize and Deserialize for GenericContainer and, if alloc is enabled,
CheckedRcRefCell.Rust 1.85, the earliest version of the 2024 edition, is supported.
Licensed under either of
at your option.
To satisfy the trait solver and avoid conflicting trait implementations,
a GenericContainer<T, C> struct is provided. It is not necessary for blanket
implementations of YourTrait for any container holding dyn YourTrait, but should be used for
blanket implementations for GenericContainer<T, C> for any container C holding a
T: ?Sized + YourTrait, or similar. ↩
Container<dyn Trait> might not be the best choice;
[FragileContainer] is preferred if possible. Just as functions are encouraged to take
FnOnce or FnMut callbacks rather than Fn (if possible), it would be best to accept
a fragile container, if the Trait's methods don't expose some potential for reentrancy with
the container holding the dyn Trait. For example, if any of a ReentrantTrait's methods
take a &self parameter and take inputs that could, potentially, have some way of getting a
reference to the wrapping [FragileContainer], then implementing such a method of
ReentrantTrait for FragileContainer<dyn ReentrantTrait> would likely begin by calling
get_ref on the container. Then, the other inputs of the method could call get_ref on
their reference to the container. Containers which are actually fragile (and don't implement
TryContainer) are probably refcounted and cloneable, so changing &self to &mut self
doesn't help: if ReentrantTrait provides a user an opportunity to run arbitrary
code inside one of its &self or &mut self methods, there's a potential problem.
As such, while FragileContainer<dyn ReentrantTrait> could be used if you are careful,
it would not work in every situation that the ReentrantTrait interface would normally
require; therefore, a blanket implementation of ReentrantTrait for anything implementing
Container<dyn ReentrantTrait> could be provided, but doing so for
FragileContainer<dyn ReentrantTrait> would make it easy to hand a fragile container to
something expecting a normal ReentrantTrait, leading to potential panics or deadlocks. ↩ ↩2