11 releases

0.10.0 Oct 21, 2022
0.9.20 May 16, 2022
0.9.19 Jan 3, 2022
0.9.17 Nov 21, 2021
0.9.2 Mar 4, 2019

#762 in Data structures


Used in ecore_rs

MIT license

43KB
755 lines

crates.io Documentation CI

safe_index

Zero-cost-wraps usize-s to give them a specific type. The motivation is to have different kinds of indices that are incompatible at type-level, thus lowering the chance of mixing them up compared to using usize-s.

Index-type creation is done through a macro, so the type actually belong to the client crate. This lets users augment index-types with methods, trait implementations, etc.

See the documentation for details.

If you are experiencing problems upgrading from a version < 0.9.17, make sure you read the changelog.


lib.rs:

Strongly-typed, zero-cost indexes wrapping integers.

This crate is just one macro: new. It creates a wrapper around usize to make type-safe indexes. That is, the indexes for your clients that you use to retrieve information efficiently from the vector of client information do not have the same type as the indexes for the files you have about your clients. The example below illustrates this crate in that context.

The index type created implements

  • Deref and From for usize,
  • Debug, Default, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash and Display.

If you are experiencing problems upgrading from a version < 0.9.17, make sure you read the changelog.

Usage

The most basic use of new is just to wrap something:

safe_index::new!{
    /// Arity.
    Arity
}
assert_eq! { core::mem::size_of::<Arity>(), core::mem::size_of::<usize>() }

This is not very useful however, there's nothing for our index to index. Thankfully new can provide more types. After the mandatory identifier Idx for the type of indexes, you can add these:

  • map <Map>: creates a wrapper named <Map> around a vector, indexed by Idx.
  • btree set <Set>: alias type for a binary tree set of Idxs.
  • btree map <Map>: alias type for a binary tree map from Idx to something.

See the examples module and the example below for illustrations of the new macro.

Example

All the code for this example is in examples::clients. Say we have a Data structure that stores some clients in a vector. It also stores files about these clients. A client can be associated to several files, and a file can be about several clients. Let's handle everything by indexes:

/// Client information.
pub struct ClientInfo {
    /// Name of the client.
    pub name: String,
    /// Indices of files associated with the client.
    pub files: BTreeSet<usize>,
}
/// File information.
pub struct FileInfo {
    /// Name of the file.
    pub name: String,
    /// Indices of clients concerned by the file.
    pub clients: BTreeSet<usize>,
}

/// Aggregates clients and files info.
pub struct Data {
    /// Map from client indexes to client information.
    pub clients: Vec<ClientInfo>,
    /// Map from file indexes to file information.
    pub files: Vec<FileInfo>,
}

Now, implementing Data's functionalities is going to be painful. Client and file indexes are both usize, terrible things are bound to happen.

So let's instead create an index type for each.

/// Indices.
pub mod idx {
    safe_index::new! {
        /// Indices of clients.
        Client,
        /// Map from clients to something (really a vector).
        map: Clients,
        /// Set of clients.
        btree set: ClientSet,
    }

    safe_index::new! {
        /// Indices of files.
        File,
        /// Map from files to something (really a vector).
        map: Files,
        /// Set of files.
        btree set: FileSet,
    }
}

use idx::*;

/// Client information.
pub struct ClientInfo {
    /// Name of the client.
    pub name: String,
    /// Indices of files associated with the client.
    pub files: ClientSet,
}
/// File information.
pub struct FileInfo {
    /// Name of the file.
    pub name: String,
    /// Indices of clients concerned by the file.
    pub clients: FileSet,
}

/// Aggregates clients and files info.
pub struct Data {
    /// Map from client indexes to client information.
    pub clients: Clients<ClientInfo>,
    /// Map from file indexes to file information.
    pub files: Files<FileInfo>,
}

The full code is available here, and you can see it used in the documentation of examples::clients. Here are a few functions on Data to (hopefully) show that Client and File behave as (and in fact are) usize indexes.

/// Aggregates clients and files info.
pub struct Data {
    /// Map from client indexes to client information.
    pub clients: Clients<ClientInfo>,
    /// Map from file indexes to file information.
    pub files: Files<FileInfo>,
}
impl Data {
    /// Adds a file, updates the clients concerned.
    pub fn add_file(&mut self, file: FileInfo) -> File {
        let idx = self.files.push(file);
        let file = &self.files[idx];
        for client in &file.clients {
            let is_new = self.clients[*client].files.insert(idx);
            debug_assert! { is_new }
        }
        idx
    }

    /// Adds a client to a file.
    pub fn add_client_to_file(&mut self, client: Client, file: File) {
        let is_new = self.files[file].clients.insert(client);
        debug_assert! { is_new }
        let is_new = self.clients[client].files.insert(file);
        debug_assert! { is_new }
    }
}

No runtime deps

Features