764038fe6e
Casualties: Any + Sync, CloneAny + Sync. Acceptable losses. |
||
---|---|---|
benches | ||
src | ||
.gitignore | ||
.travis.yml | ||
Cargo.toml | ||
CHANGELOG.md | ||
COPYING | ||
README.md |
AnyMap
, a safe and convenient store for one value of each type
If you’re familiar with Go and Go web frameworks, you may have come across the common “environment” pattern for storing data related to the request. It’s typically something like map[string]interface{}
and is accessed with arbitrary strings which may clash and type assertions which are a little unwieldy and must be used very carefully. (Personally I would consider that it is just asking for things to blow up in your face.) In a language like Go, lacking in generics, this is the best that can be done; such a thing cannot possibly be made safe without generics.
As another example of such an interface, JavaScript objects are exactly the same—a mapping of string keys to arbitrary values. (There it is actually more dangerous, because methods and fields/attributes/properties are on the same plane.)
Fortunately, we can do better than these things in Rust. Our type system is quite equal to easy, robust expression of such problems.
The AnyMap
type is a friendly wrapper around a HashMap<TypeId, Box<dyn Any>>
, exposing a nice, easy typed interface, perfectly safe and absolutely robust.
What this means is that in an AnyMap
you may store zero or one values for every type.
Unsafe code in this library
This library uses a fair bit of unsafe code for several reasons:
-
To support Any and CloneAny, unsafe code is required (because of how the
downcast
methods are defined inimpl dyn Any
rather than being trait methods; I think this is kind of a historical detail of the structure ofstd::any::Any
); if you wanted to ditchClone
support this unsafety could be removed. -
In the interests of performance, skipping various checks that are unnecessary because of the invariants of the data structure (no need to check the type ID when it’s been statically ensured by being used as the hash map key).
-
In the
Clone
implementation ofdyn CloneAny
withSend
and/orSync
auto traits added, an unsafe block is used where safe code used to be used in order to avoid a spurious future-compatibility lint.
It’s not possible to remove all unsafety from this library without also removing some of the functionality. Still, at the cost of the CloneAny
functionality and the raw interface, you can definitely remove all unsafe code. Here’s how you could do it:
- Remove the genericness of it all;
- Merge
anymap::raw
into the normal interface, flattening it; - Change things like
.map(|any| unsafe { any.downcast_unchecked() })
to.and_then(|any| any.downcast())
(performance cost: one extra superfluous type ID comparison, indirect).
Yeah, the performance costs of going safe are quite small. The more serious matter is the loss of Clone
.
But frankly, if you wanted to do all this it’d be easier and faster to write it from scratch. The core of the library is actually really simple and perfectly safe, as can be seen in src/lib.rs
in the first commit (note that that code won’t run without a few syntactic alterations; it was from well before Rust 1.0 and has things like Any:'static
where now we have Any + 'static
).
Colophon
Authorship: Chris Morgan is the author and maintainer of this library.
Licensing: this library is distributed under the terms of the Blue Oak Model License 1.0.0, the MIT License and the Apache License, Version 2.0, at your choice. See COPYING for details.