Twonky
Twonky

Reputation: 806

Avoid using partially moved value in match statement

The solution provided in question 38553513 does not work for me.

Consider these types and functions:

pub enum Parameter {
    ParameterTypeA { n: i32 },
    ParameterTypeB { s: String },
}

pub struct Res {
    pub param: Parameter,
    pub res: i32,
}

n use_string(_: String) -> i32 {
    return 23;
}

fn use_int(_: i32) -> i32 {
    return 42;
}

What I wanted to achieve was this:

fn foobar(p: Parameter) -> std::result::Result<Res, String> {
    return match p {
        Parameter::ParameterTypeA { n } => { Ok(Result { param: p, res: use_int(n) }) }
        Parameter::ParameterTypeB { s } => { Ok(Result { param: p, res: use_string(s) }) }
    };
}

But the compiler complained about Parameter::ParameterTypeB { s } being partially moved and refuses to use it with param: p.

According to the compiler's suggestion and the mentioned SO thread, I changed the second match to

Parameter::ParameterTypeB { ref s } => { Ok(Result { param: p, res: use_string(s.to_string()) }) }

but again, the compiler complains that {ref s} borrows p.s which cannot be moved while creating the result.

How is should this problem be solved? Please understand, modifying the function signatures or data types is not an option.

MVE available at play.rust-lang.org.

Upvotes: 0

Views: 102

Answers (1)

Jmb
Jmb

Reputation: 23319

The most idiomatic way to fix this would be to change the signature of use_string so that it takes a &str instead of a String:

pub enum Parameter {
    ParameterTypeA { n: i32 },
    ParameterTypeB { s: String },
}

pub struct Res {
    pub param: Parameter,
    pub res: i32,
}

fn use_string(_: &str) -> i32 {
    return 23;
}

fn use_int(_: i32) -> i32 {
    return 42;
}

fn foobar(p: Parameter) -> std::result::Result<Res, String> {
    return match p {
        Parameter::ParameterTypeA { n } => { Ok(Res { param: p, res: use_int(n) }) }
        Parameter::ParameterTypeB { ref s } => { Ok(Res { res: use_string (s), param: p }) }
    };
}

Playground

Note that I've changed the order of res and param so that res comes first when instantiating Res. This is because they are evaluated from left to right, and having param: p first would move p and make it impossible to use s (which borrows p) in the subsequent call to use_string.

Since you state that changing the signature is not an option, you will need to clone the string so that p retains a copy after the call to use_string:

pub enum Parameter {
    ParameterTypeA { n: i32 },
    ParameterTypeB { s: String },
}

pub struct Res {
    pub param: Parameter,
    pub res: i32,
}

fn use_string(_: String) -> i32 {
    return 23;
}

fn use_int(_: i32) -> i32 {
    return 42;
}

fn foobar(p: Parameter) -> std::result::Result<Res, String> {
    return match p {
        Parameter::ParameterTypeA { n } => { Ok(Res { param: p, res: use_int(n) }) }
        Parameter::ParameterTypeB { ref s } => { Ok(Res { res: use_string (s.clone()), param: p }) }
    };
}

Playground

Upvotes: 1

Related Questions