#interior-mutability #slice #mutable #zero-allocation #refcell #run-time #elements

indices

Indices provides macros and methods for safely retrieving multiple mutable elements from a mutable slice, addressing scenarios where slice elements would typically require RefCell or Cell (interior mutability approach)

10 releases

0.3.6 Jul 21, 2024
0.3.5 Jul 21, 2024
0.3.1 Apr 23, 2024
0.2.1 Apr 22, 2024
0.1.0 Apr 21, 2024

#377 in Rust patterns

Download history 10/week @ 2024-08-10 15/week @ 2024-08-17 4/week @ 2024-08-24 1/week @ 2024-08-31 54/week @ 2024-09-07 69/week @ 2024-09-14 41/week @ 2024-09-21 44/week @ 2024-09-28 27/week @ 2024-10-05 49/week @ 2024-10-12 58/week @ 2024-10-19 197/week @ 2024-10-26 230/week @ 2024-11-02 102/week @ 2024-11-09 120/week @ 2024-11-16 274/week @ 2024-11-23

753 downloads per month
Used in 8 crates (2 directly)

Apache-2.0

54KB
1K SLoC

Indices

github crates.io docs.rs build status

Indices provides macros and methods for safely retrieving multiple mutable elements from a mutable slice, addressing scenarios where slice elements would typically require RefCell or Cell (interior mutability approach).

e.g.

let (four, one, two) = indices!(slice, 4, 1, 2);

Which expands to

#[inline(always)]
fn func<T>(slice: &mut [T], one: usize, two: usize, three: usize,) -> (&mut T, &mut T, &mut T) {
    if one == two || one == three || two == three {
        panic!("Duplicate indices are not allowed.");
    }
    let slice_len = slice.len();
    if one >= slice_len || two >= slice_len || three >= slice_len {
        panic!("Index out of bounds.");
    }
    let ptr = slice.as_mut_ptr();
    unsafe { (&mut *ptr.add(one), &mut *ptr.add(two), &mut *ptr.add(three)) }
}
let (four, one, two) = func(slice, 4, 1, 2);

Which will be optimized by the rust compiler to essentially the following pseudo code

if 4 >= slice.len() {
    panic!("Index out of bounds.");
}
let (four, one, two) = (&mut slice[4], &mut slice[1], &mut slice[2])

The above code is safe, correct, and more performant than using RefCell or Cell. indices! follows the above expansion pattern for up to 4 requested indices. At which point, the macro will switch to a more optimized approach for many requested indices.

There is also try_indices!, indices_ordered!, and try_indices_ordered!.

Examples

Macro Example

All macros are zero allocation and allow retrieving a variable number of indices at runtime. Prefer macros when the number of indices are known at compile time. e.g.

fn main() {
    struct Person {
        first: String,
        last: String,
    }
    let mut data = [
        Person { first: "John".to_string(), last: "Doe".to_string() },
        Person { first: "Jane".to_string(), last: "Smith".to_string() },
        Person { first: "Alice".to_string(), last: "Johnson".to_string() },
        Person { first: "Bob".to_string(), last: "Brown".to_string() },
        Person { first: "Charlie".to_string(), last: "White".to_string() },
    ];
    fn modify(data_slice: &mut [Person], index: usize){
        let (four, func_provided, three) = indices!(data_slice, 4, index, 3);
        four.last = "Black".to_string();
        func_provided.first = "Jack".to_string();
        three.last = "Jones".to_string();
    }
    let slice = data.as_mut_slice();
    modify(slice, 1);
    assert_eq!(data[4].last, "Black");
    assert_eq!(data[1].first, "Jack");
    assert_eq!(data[3].last, "Jones");
}
Method Example

Methods allow for more dynamic runtime retrieval when the number of indices is unknown at compile time. e.g.

fn main() {
    struct Node {
        index: usize,
        visited: usize,
        edges: Vec<usize>,
        message: String,
    }

    let mut graph = vec![
        Node {
            index: 0,
            visited: usize::MAX,
            edges: vec![1, 2],
            message: String::new(),
        },
        Node {
            index: 1,
            visited: usize::MAX,
            edges: vec![0, 2],
            message: String::new(),
        },
        Node {
            index: 2,
            visited: usize::MAX,
            edges: vec![3],
            message: String::new(),
        },
        Node {
            index: 4,
            visited: usize::MAX,
            edges: vec![1],
            message: String::new(),
        },
    ];

    fn traverse_graph(graph: &mut [Node], current: usize, start: usize) -> bool {
        if current == start {
            return true;
        }
        let edges = graph[current].edges.clone();
        let [mut current_node, mut edge_nodes] = indices_slices(graph, [&[current], &edges]);
        for edge_node in edge_nodes.iter_mut() {
            current_node[0].visited = current;
            edge_node.message.push_str(&format!(
                "This is Node `{}` Came from Node `{}`.",
                edge_node.index, current_node[0].visited
            ));
        }
        for edge in edges {
            if traverse_graph(graph, edge, start) {
                return true;
            }
        }
        return false;
    }
    traverse_graph(&mut *graph, 2, 0);
    let answers = [
        "This is Node `0` Came from Node `1`.",
        "This is Node `1` Came from Node `3`.",
        "This is Node `2` Came from Node `1`.",
        "This is Node `4` Came from Node `2`.",
    ];
    for (index, node) in graph.iter().enumerate() {
        assert_eq!(&node.message, answers[index]);
    }
}

No runtime deps