4

I have a fixed size array of Strings: [String; 2]. I want to turn it into a (String, String). Can I do this without copying the values?

The piece of code that I'm working on in particular is the following:

let (basis, names_0, names_1) = if let Some(names) = self.arg_name {
    (ComparisonBasis::Name, names[0], names[1])
} else {
    (ComparisonBasis::File, self.arg_file[0], self.arg_file[1])
};

types:

self.arg_name: Option<[String; 2]>
self.arg_file: Vec<String>

Right now I'm getting errors

cannot move out of type `[std::string::String; 2]`, a non-copy fixed-size array [E0508]

and

cannot move out of indexed content [E0507]

for the two arms of the if

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Apanatshka
  • 5,958
  • 27
  • 38
  • In the end, I guess it's a bit overzealous of me to try to optimise out the calls to clone. The piece of code in the question is called exactly once per program run and it's just two (probably small) strings. – Apanatshka Jul 10 '16 at 15:01

2 Answers2

4

You've omitted a fair amount of context, so I'm taking a guess at a few aspects. I'm also hewing a little closer to the question you asked, rather than the vaguer one implied by your snippets.

struct NeverSpecified {
    arg_names: Option<[String; 2]>,
    arg_file: Vec<String>,
}

impl NeverSpecified {
    fn some_method_i_guess(mut self) -> (String, String) {
        if let Some(mut names) = self.arg_names {
            use std::mem::replace;
            let name_0 = replace(&mut names[0], String::new());
            let name_1 = replace(&mut names[1], String::new());
            (name_0, name_1)
        } else {
            let mut names = self.arg_file.drain(0..2);
            let name_0 = names.next().expect("expected 2 names, got 0");
            let name_1 = names.next().expect("expected 2 names, got 1");
            (name_0, name_1)
        }
    }
}

I use std::mem::replace to switch the contents of the array, whilst leaving it in a valid state. This is necessary because Rust won't allow you to have a "partially valid" array. There are no copies or allocations involved in this path.

In the other path, we have to pull elements out of the vector by hand. Again, you can't just move values out of a container via indexing (this is actually a limitation of indexing overall). Instead, I use Vec::drain to essentially chop the first two elements out of the vector, then extract them from the resulting iterator. To be clear: this path doesn't involve any copies or allocations, either.

As an aside, those expect methods shouldn't ever be triggered (since drain does bounds checking), but better paranoid than sorry; if you want to replace them with unwrap() calls instead, that should be fine..

DK.
  • 55,277
  • 5
  • 189
  • 162
  • This is exactly what I was looking for :) Sorry for the vagueness in the question. – Apanatshka Jul 10 '16 at 15:00
  • @Apanatshka: Just to be clear, neither branch in the example involves copying or allocating; it's *purely* moves (and empty values in the first branch). Just wanted to clarify since your comment on your question could imply you think there's no way to do this without copying. – DK. Jul 10 '16 at 16:11
  • I understand that your example doesn't do any copying (but it does allocate new (empty) `String`s right?). I just decided that in this case a call to `clone` is fine, and slightly less effort to write and read :) – Apanatshka Jul 11 '16 at 17:06
  • 2
    @Apanatshka it creates empty strings, but doing so does *not* involve any actual allocation. – DK. Jul 11 '16 at 17:16
1

Since Rust 1.36, you can use slice patterns to bind to all the values of the array at once:

struct NeverSpecified {
    arg_names: Option<[String; 2]>,
    arg_file: Vec<String>,
}

impl NeverSpecified {
    fn some_method_i_guess(mut self) -> (String, String) {
        if let Some([name_0, name_1]) = self.arg_names.take() {
            (name_0, name_1)
        } else {
            let mut names = self.arg_file.drain(0..2);
            let name_0 = names.next().expect("expected 2 names, got 0");
            let name_1 = names.next().expect("expected 2 names, got 1");
            (name_0, name_1)
        }
    }
}

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366