Need help with collecting the array of arrays of tuples

Hi everyone !

I want to optimize my app and according to the optimization process I want to make my Modal to accept &[ &[ (&str, &str) ] ]:

type NamedValue<'a> = (&'a str, &'a str);
type NamedValueList<'a> = &'a [NamedValue<'a>];

#[derive(Default, Clone, Copy)]
pub struct Modal<'a> {
    title: &'a str,
    items: &'a [NamedValueList<'a>],
}

impl<'a> Modal<'a> {
    pub fn new(title: &'a str, items: &'a [NamedValueList<'a>]) -> Self {
        Self {
            title,
            items,
        }
    }
}

but when I pass some data into the modal:

HandlerOutput::TransferCharactersList(characters) => {
    let items = characters.iter().map(|c| {
        [
            ("Guid:", c.guid.to_string().as_str()),
            ("name:", c.name.as_str())
        ]
    }).collect();

    *modal.lock().await = Modal::new(
        "SELECT CHARACTER",
        items,
    )
}

I got such error:

error[E0277]: a value of type `&[&[(&str, &str)]]` cannot be built from an iterator over elements of type `[(&str, &str); 2]`
    --> src/features/ui2/mod.rs:73:40
     |
73   | ...                   }).collect();
     |                          ^^^^^^^ value of type `&[&[(&str, &str)]]` cannot be built from `std::iter::Iterator<Item=[(&str, &str); 2]>`
     |
     = help: the trait `FromIterator<[(&str, &str); 2]>` is not implemented for `&[&[(&str, &str)]]`
note: the method call chain might not have had the expected associated types
    --> src/features/ui2/mod.rs:68:87
     |
68   |   ...                   let items: &[&[(&str, &str)]] = characters.iter().map(|c| {
     |  _______________________________________________________----------_------_^
     | |                                                       |          |
     | |                                                       |          `Iterator::Item` is `&Object` here
     | |                                                       this expression has type `Vec<Object>`
69   | | ...                       [
70   | | ...                           ("Guid:", c.guid.to_string().as_str()),
71   | | ...                           ("name:", c.name.as_str())
72   | | ...                       ]
73   | | ...                   }).collect();
     | |________________________^ `Iterator::Item` changed to `[(&str, &str); 2]` here
note: required by a bound in `std::iter::Iterator::collect`

Could somebody explain, how can I fix this issue ?

Even when we were to collect into a Vec<&[(&str, &str)]>, which would implement FromIterator and thus satisfying the bound on collect, this still wouldn't work, as we are trying to return references to two temporary values created in map (the slice &[(&str, &str)] and c.guid.to_string().as_str() also references a temporary). I'm a bit mystified by the API, so I wouldn't know how much of a help this is, but here a version of your code that compiles by using owned types to avoid references to temporaries:

type NamedValue<'a> = (&'a str, String);
type NamedValueList<'a> = [NamedValue<'a>; 2];

struct Character {
    guid: String,
    name: String,
}

fn main() {
    let characters: Vec<Character> = Vec::new();

    let _items: Vec<NamedValueList> = characters
        .iter()
        .map(|c| [("Guid:", c.guid.to_string()), ("name:", c.name.clone())])
        .collect();
}

Playground.

3 Likes

Actually the size of array can be different, not necessary 2. What I want to pass is array of arrays, where each nested array contains any amount of tuples. Each tuple is actually label + value pair.

I just want to avoid of using vector at this point since I do not need to modify the characters list (insert/delete etc). I just need to transform this list, since characters are just Vec<Object>.

And all I want after I passed the characters list to the modal is to build a list item for each array of tuples:

pub fn build_item(&self, parts: NamedValueList) -> Line {
        let mut spans = Vec::new();

        for (label, name) in parts.iter() {
            spans.extend_from_slice(&[
                Span::styled(
                    label,
                    Style::default().fg(Color::Magenta).add_modifier(Modifier::BOLD),
                ),
                Span::styled(
                    name,
                    Style::default().fg(Color::Green),
                )
            ]);
        }

        Line::from(spans)
    }

So, from my understanding it is impossible to collect into array when size is unknown :frowning:

An array length must be known at compile time, yes. This is part of the definition of an array, and is not specific to collecting.

1 Like

As an attempt to avoid of using vectors I also tried to use iterators:

type NamedValue<'a> = (&'a str, &'a str);

pub fn zip<'a>(
    labels: &'a [&'a str],
    values: &'a [&'a str],
) -> impl Iterator<Item=impl Iterator<Item=NamedValue<'a>> + 'a> {
    values.chunks(labels.len()).map(move |chunk| chunk.iter().zip(labels.iter()).map(|(&l, &v)| (l, v)))
}

fn main() {
    let x = vec!["a", "b", "c", "d", "e", "f"];
    let labels = ["name", "value", "test"];

    for row in zip(&labels, &x) {
        println!("{:?}", row.collect::<Vec<_>>());
    }
}

I can configure the labels here so I can get:

[("a", "name"), ("b", "value"), ("c", "test")]
[("d", "name"), ("e", "value"), ("f", "test")]

or set labels to "name", "value" and get:

[("a", "name"), ("b", "value")]
[("c", "name"), ("d", "value")]
[("e", "name"), ("f", "value")]

The question is will this be a good solution.

I don't see anything wrong with it. But it depends on whether the iterator will work for whatever you want to do with the data. Maybe that's what you meant.