Generics that can accept both a multi-dimensional array or Vec

Hello,

I'm wondering about opinions for trait bounds people like to use for multi-dimensional nested sequence data, so the bounds cover both arrays and Vecs. Specifically this situation:

use core::borrow::Borrow;
use core::hash::Hash;
use std::collections::HashMap;

fn access_multi_dimensional<'a, OuterV, InnerV, S>(multi: OuterV )
    where
    OuterV: AsRef<[InnerV]>,
    InnerV: AsRef<[&'a S]>,
    String: Borrow<S>,
    S: Hash + Eq + ?Sized + 'a,
{
    let map: HashMap<String, f64> = HashMap::new();
    
    for outers in multi.as_ref().into_iter() {
        for inner in outers.as_ref().into_iter() {
            let _ = map.get(inner);
        }
    }
}

const MULTI: &[&[&str]] = &[
    &["one", "two"],
    &["A", "B"],
];

fn main() {
    
    let multi = vec![
        vec!["one".to_string(), "two".to_string()],
        vec!["A".to_string(), "B".to_string()]
    ];

    access_multi_dimensional(&MULTI);
    access_multi_dimensional(&multi);
    
}

This is more an opinion / style question because I can think of several way to make it work (Specifically, I could 1. define my own trait or 2. use Iterator traits for the bound and use iter::Map objects when calling the function), but both of those approaches are ugly or limited.

I was curious if I'm missing something - ie a way to do this elegantly with the functionality already in core / std.

Thank you.

It'd probably be easier to just borrow into a concrete type, likely str, like this:

fn access_multi_dimensional<OuterV, InnerV, S>(multi: OuterV )
where
    OuterV: AsRef<[InnerV]>,
    InnerV: AsRef<[S]>,
    S: Hash + Eq + Borrow<str>,
{
    let map: HashMap<String, f64> = HashMap::new();
    
    for outers in multi.as_ref() {
        for inner in outers.as_ref() {
            let _ = map.get(inner.borrow());
        }
    }
}
1 Like

There are two options I would normally use if I need to work some sort of 2D matrix that might be backed by a vec or a multidimensional array, depending on the particular guarantees and interface I want to have.

One option is to create some sort of Matrix type which abstracts over a Vec<Vec<T>> or a [[T; N]; M] and hides the details from the user. Internally, it could be an enum or whatever, and you can implement nice things like Index<(usize, usize)>, a matrix.rows() iterator, map(), and so on.

Another option is to introduce a trait which provides the behaviour you want and then write your code against that abstraction.

I don't think you'll want to go down the where OuterV: AsRef<[InnerV]>, InnerV: AsRef<[S]> path for anything more than a once-off function. It'll make your code harder to read because you've got half a dozen extra lines of where-clauses, and even then you might find it isn't flexible enough[1].


  1. One situation I've seen several times is to have some sort of 2D matrix type where you want to get sub-matrices. This can't be represented using the AsRef slice method because each row in the sub-matrix isn't stored contiguously in memory, instead it'll often skip chunks of memory. ↩ī¸Ž

1 Like

I totally see your point about unwieldy bounds and the value of a dedicated type / trait. But in this particular case I was looking for for generic bound by std / core traits.

But in general I think you're right and I appreciate your advice. Thank you.

I think the closest you'll get is AsRef or chaining something together with iterators.

The Rust standard library's philosophy is to only have traits that are needed by the language (e.g. Copy and Future), interfaces to fundamental OS features (e.g. std::io and std::fs) or which would be required for almost every program out there (e.g. HashMap and Vec). It tries to avoid things which would be useful in a handful of use cases. There isn't any fundamental part of Rust that needs to abstract over array-like things, and there are actually a lot of equally valid ways that abstraction could be implemented, so we prefer to iterate on it in the ecosystem instead.

That's why you won't find things like compression algorithms, crypto, matrices, and images in std for example.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.