X-Git-Url: https://git.chrismorgan.info/anymap/blobdiff_plain/ea63cc90d12bb126b40d2d365314ae4d900b920b..e45f984c83984b45f86ea7a5d5bac6d5aea5f483:/README.md diff --git a/README.md b/README.md index ffdbe32..798f925 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,82 @@ -``AnyMap``, a safe and convenient store for one value of each type -================================================================== +# ``AnyMap``, a safe and convenient store for one value of each type -[![Build Status](https://travis-ci.org/chris-morgan/anymap.svg?branch=master)](https://travis-ci.org/chris-morgan/anymap) +``AnyMap`` is a type-safe wrapper around ``HashMap>`` that lets you not worry about ``TypeId`` or downcasting, but just get on with storing one each of a bag of diverse types, which is really useful for extensibility in some sorts of libraries. -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. +## Background -This is madness. Hare-brained, stark, raving madness, just *asking* for things to blow up in your face. Unfortunately for people in Go, it’s the best that they can have because of its weak type system; such a thing cannot possibly be made safe without generics. +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. -Fortunately, we can do better in Rust. Our type system is quite equal to easy, robust expression of such problems. +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—though it’s *possible* to use `Map` these days.) -The ``AnyMap`` type is a friendly wrapper around a ``HashMap:'static>``, exposing a nice, easy typed interface, perfectly safe and absolutely robust. +Fortunately, we can do better than these things in Rust. Our type system is quite equal to easy, robust expression of such problems. -What this means is that in an ``AnyMap`` you may store zero or one values for every type. +## Example -Instructions ------------- +```rust +let mut data = anymap::AnyMap::new(); +assert_eq!(data.get(), None::<&i32>); +data.insert(42i32); +assert_eq!(data.get(), Some(&42i32)); +data.remove::(); +assert_eq!(data.get::(), None); - make +#[derive(Clone, PartialEq, Debug)] +struct Foo { + str: String, +} -Future work ------------ +assert_eq!(data.get::(), None); +data.insert(Foo { str: format!("foo") }); +assert_eq!(data.get(), Some(&Foo { str: format!("foo") })); +data.get_mut::().map(|foo| foo.str.push('t')); +assert_eq!(&*data.get::().unwrap().str, "foot"); +``` -I think that the only thing left for this is filling out additional methods from ``HashMap`` as appropriate. +## Features -It’s a very simple thing. +- Store up to one value for each type in a bag. +- Add `Send` or `Send + Sync` bounds. +- You can opt into making the map `Clone`. (In theory you could add all kinds of other functionality, but you can’t readily make this work *generically*, and the bones of it are simple enough that it becomes better to make your own extension of `Any` and reimplement `AnyMap`.) +- no_std if you like. -Author ------- +## Cargo features/dependencies/usage -[Chris Morgan](http://chrismorgan.info/) ([chris-morgan](https://github.com/chris-morgan)) is the primary author and maintainer of AnyMap. +Typical Cargo.toml usage: -License -------- +```toml +[dependencies] +anymap = "1.0.0-beta.1" +``` -This library is distributed under similar terms to Rust: dual licensed under the MIT license and the Apache license (version 2.0). +No-std usage, using `alloc` and the [hashbrown](https://rust-lang.github.io/hashbrown) crate instead of `std::collections::HashMap`: -See LICENSE-APACHE, LICENSE-MIT, and COPYRIGHT for details. +```toml +[dependencies] +anymap = { version = "1.0.0-beta.1", default-features = false, features = ["hashbrown"] } +``` + +The `std` feature is enabled by default. The `hashbrown` feature overrides it. At least one of the two must be enabled. + +**On stability:** hashbrown is still pre-1.0.0 and experiencing breaking changes. Because it’s useful for a small fraction of users, I am retaining it, but with *different compatibility guarantees to the typical SemVer ones*. Where possible, I will just widen the range for new releases of hashbrown, but if an incompatible change occurs, I may drop support for older versions of hashbrown with a bump to the *minor* part of the anymap version number (e.g. 1.1.0, 1.2.0). Iff you’re using this feature, this is cause to *consider* using a tilde requirement like `"~1.0"` (or spell it out as `>=1, <1.1`). + +## Unsafe code in this library + +This library uses a fair bit of unsafe code for several reasons: + +- To support `CloneAny`, unsafe code is currently required (because the downcast methods are defined on `dyn Any` rather than being trait methods, and upcasting is an incomplete feature in rustc); if you wanted to ditch `Clone` support this unsafety could be removed. + +- For `dyn CloneAny + Send` and `dyn CloneAny + Send + Sync`’s `Clone` implementation, an unsafe block is used to attach the auto traits where safe code used to be used, in order to avoid a [spurious future-compatibility lint](https://github.com/rust-lang/rust/issues/51443#issuecomment-421988013). + +- In the interests of performance, type ID checks are skipped as unnecessary because of the invariants of the data structure (though this does come at the cost of `Map::{as_raw_mut, into_raw}` being marked unsafe). + +It is possible to remove all unsafe code at the cost of only `CloneAny` functionality and a little performance. The `safe` branch in the Git repository contains a couple of commits demonstrating the concept. It’s quite straightforward; the core of this library is very simple and perfectly safe. + +## Colophon + +**Authorship:** [Chris Morgan](https://chrismorgan.info/) 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](https://blueoakcouncil.org/license/1.0.0), the +[MIT License](https://opensource.org/licenses/MIT) and the +[Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0), at your choice. +See [COPYING](COPYING) for details.