Skip to content

Zero allocation macros for retrieving multiple mutable indices from a mutable slice safely.

License

Notifications You must be signed in to change notification settings

mcmah309/indices

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

86 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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);

The above code is safe, correct, and more performant than using RefCell or Cell. It 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) = (slice.get_unchecked_mut(4), slice.get_unchecked_mut(1), slice.get_unchecked_mut(2))

indices! follows the previous 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]);
    }
}

About

Zero allocation macros for retrieving multiple mutable indices from a mutable slice safely.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages