Listerone
Listerone

Reputation: 1631

How to move values out of a vector when the vector is immediately discarded?

I am receiving data in the form of a string vector, and need to populate a struct using a subset of the values, like this:

const json: &str = r#"["a", "b", "c", "d", "e", "f", "g"]"#;

struct A {
    third: String,
    first: String,
    fifth: String,
}

fn main() {
    let data: Vec<String> = serde_json::from_str(json).unwrap();
    let a = A {
        third: data[2],
        first: data[0],
        fifth: data[4],
    };
}

This doesn't work because I'm moving values out of the vector. The compiler believes that this leaves data in an uninitialized state that can cause problems, but because I never use data again, it shouldn't matter.

The conventional solution is swap_remove, but it is problematic because the elements are not accessed in reverse order (assuming the structure is populated top to bottom).

I solve this now by doing a mem::replace and having data as mut, which clutters this otherwise clean code:

fn main() {
    let mut data: Vec<String> = serde_json::from_str(json).unwrap();
    let a = A {
        third: std::mem::replace(&mut data[2], "".to_string()),
        first: std::mem::replace(&mut data[0], "".to_string()),
        fifth: std::mem::replace(&mut data[4], "".to_string())
    };
}

Is there an alternative to this solution that doesn't require me to have all these replace calls and data unnecessarily mut?

Upvotes: 10

Views: 3251

Answers (3)

Sven Marnach
Sven Marnach

Reputation: 602715

Another option is to use a vector of Option<String>. This allows us to move the values out, while keeping track of what values have been moved, so they are not dropped with the vector.

let mut data: Vec<Option<String>> = serde_json::from_str(json).unwrap();
let a = A {
    third: data[2].take().unwrap(),
    first: data[0].take().unwrap(),
    fifth: data[4].take().unwrap(),
};

Upvotes: 1

Shepmaster
Shepmaster

Reputation: 432189

In small cases like this (also seen in naïve command line argument processing), I transfer ownership of the vector into an iterator and pop all the values off, keeping those I'm interested in:

fn main() {
    let data: Vec<String> = serde_json::from_str(json).unwrap();
    let mut data = data.into_iter().fuse();

    let first = data.next().expect("Needed five elements, missing the first");
    let _ = data.next();
    let third = data.next().expect("Needed five elements, missing the third");
    let _ = data.next();
    let fifth = data.next().expect("Needed five elements, missing the fifth");

    let a = A {
        third,
        first,
        fifth,
    };
}

I'd challenge the requirement to have a vector, however. Using a tuple is simpler and avoids much of the error handling needed, if you have exactly 5 elements:

fn main() {
    let data: (String, String, String, String, String) = serde_json::from_str(json).unwrap();

    let a = A {
        third: data.2,
        first: data.0,
        fifth: data.4,
    };
}

See also:

Upvotes: 4

Boiethios
Boiethios

Reputation: 42889

I've been in this situation, and the cleanest solution I've found was to create an extension:

trait Extract: Default {
    /// Replace self with default and returns the initial value.
    fn extract(&mut self) -> Self;
}

impl<T: Default> Extract for T {
    fn extract(&mut self) -> Self {
        std::mem::replace(self, T::default())
    }
}

And in your solution, you can replace the std::mem::replace with it:

const JSON: &str = r#"["a", "b", "c", "d", "e", "f", "g"]"#;

struct A {
    third: String,
    first: String,
    fifth: String,
}

fn main() {
    let mut data: Vec<String> = serde_json::from_str(JSON).unwrap();
    let _a = A {
        third: data[2].extract(),
        first: data[0].extract(),
        fifth: data[4].extract(),
    };
}

That's basically the same code, but it is much more readable.


If you like funny things, you can even write a macro:

macro_rules! vec_destruc {
    { $v:expr => $( $n:ident : $i:expr; )+ } => {
        let ( $( $n ),+ ) = {
            let mut v = $v;
            (
                $( std::mem::replace(&mut v[$i], Default::default()) ),+
            )
        };
    }
}

const JSON: &str = r#"["a", "b", "c", "d", "e", "f", "g"]"#;

#[derive(Debug)]
struct A {
    third: String,
    first: String,
    fifth: String,
}

fn main() {
    let data: Vec<String> = serde_json::from_str(JSON).unwrap();

    vec_destruc! { data =>
        first: 0;
        third: 2;
        fifth: 4;
    };
    let a = A { first, third, fifth };

    println!("{:?}", a);
}

Upvotes: 6

Related Questions