Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 495
Listerone Avatar asked Aug 28 '19 05:08

Listerone


1 Answers

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);
}
like image 136
Boiethios Avatar answered Sep 27 '22 20:09

Boiethios