4 releases

✓ Uses Rust 2018 edition

0.9.3 Sep 17, 2019
0.9.2 Mar 4, 2019
0.9.1 Mar 4, 2019
0.9.0 Mar 4, 2019

#339 in Data structures

39 downloads per month

MIT license

35KB
606 lines

safe_index

linux windows
Build Status Build status Latest Version codecov

Type-safe indexing in Rust. Provides a zero-cost way to have different types of indexes. See the documentation for more details.


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, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash and Display.

Usage

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

safe_index::new!{
    /// Arity.
    Arity
}
assert_eq! { std::mem::size_of::<Arity>(), std::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:

  • range <Range>: creates an iterator named <Range> between two Idxs (the upper bound is exclusive). If this constructor is present, Idx will have a up_to function that creates a range between two Idxs. This constructor can only appear once.
  • map <Map> with iter: <MapIter>: creates a wrapper named <Map> around a vector, indexed by Idx. <MapIter> is the type of iterators over <Map>.
  • 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:

# use std::collections::BTreeSet;
/// 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 with iter: ClientIter,
        /// Set of clients.
        btree set: ClientSet,
    }

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

use idx::*;

# use std::collections::BTreeSet;
/// 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.

# use safe_index::examples::clients::{idx::*, ClientInfo, FileInfo};
/// 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.next_index();
        for client in &file.clients {
            let is_new = self.clients[*client].files.insert(idx);
            debug_assert! { is_new }
        }
        let nu_idx = self.files.push(file);
        debug_assert_eq! { idx, nu_idx }
        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