Leo Borai
Leo Borai

Reputation: 2509

Rust: Mutate an Option field whose wrapped value doesnt implement the Copy trait

I have a Message struct which have a field I want to modify when calling a method on it.

This Message struct have an attachments field, which is an Option<Vec<Attachment>>.

My goal is to be able to call a method on Message to push an Attachment object to the attachments Vec.

#[derive(Debug, Serialize, Deserialize)]
pub struct Message {
    /// The verified sender email address
    #[serde(rename = "FromEmail")]
    pub from_email: String,
    /// The name of the sender
    #[serde(rename = "FromName")]
    pub from_name: String,
    /// The subject of the email
    #[serde(rename = "Subject")]
    pub subject: Option<String>,
    /// The raw text content of the email
    #[serde(rename = "Text-part")]
    pub text_part: String,
    /// The HTML content of the email
    #[serde(rename = "Html-part")]
    pub html_part: Option<String>,
    #[serde(rename = "Recipients")]
    pub recipients: Vec<Recipient>,
    #[serde(rename = "Attachments")]
    pub attachments: Option<Vec<Attachment>>,
    #[serde(rename = "Inline_attachments")]
    pub inline_attachments: Option<Vec<InlineAttachments>>,
}

The following is my current implementation on the method that will push Attachment objects to the Option<Vec<Attachment>>:

    pub fn attach(&mut self, attachment: Attachment) {
        // if theres no attachments already
        // initialize the attachments vector
        if self.attachments.is_none() {
            let mut attachments = Vec::new();

            attachments.push(attachment);
            self.attachments = Some(attachments);
        } else {
            // Where this is invalid as theres no `clone` for `Option<Vec<Attachment>>`

            let attachments = self.attachments.clone();

            attachments.push(attachment);
        }
    }

This is the implementation for the Attachment struct:

use serde::{Deserialize, Serialize};

/// An email attachment
#[derive(Debug, Serialize, Deserialize)]
pub struct Attachment {
  #[serde(rename = "Content-type")]
  pub content_type: String,
  #[serde(rename = "Filename")]
  pub filename: String,
  pub content: String,
}

The issue is on the else block, where the attachents Option field is unwrapped, then an attachment object is pushed to the Vec.

The problem is that Attachment is unable to implement the Copy trait.

Which is the correct approach for scenarios like this in Rust?

Thanks in advance!

Upvotes: 1

Views: 787

Answers (1)

bk2204
bk2204

Reputation: 76774

Option has a wide variety of helper methods which can help here. You basically want to mutate the Vec that's already there if there is one, and if not, create one and mutate it. You can do that with Option::get_or_insert_with, which returns a &mut Vec<Attachment>. With that, you can push the item onto the end. A minimal compilable example looks like this:

pub struct Message {
    pub attachments: Option<Vec<Attachment>>,
}

#[derive(Debug)]
pub struct Attachment {
    pub content_type: String,
    pub filename: String,
    pub content: String,
}

impl Message {
    pub fn attach(&mut self, attachment: Attachment) {
        self.attachments
            .get_or_insert_with(Vec::new)
            .push(attachment);
    }
}

fn main() {
    let mut m = Message { attachments: None };
    let a = Attachment {
        content_type: "application/json".into(),
        filename: "foo.json".into(),
        content: "{}".into(),
    };
    m.attach(a);
    assert_eq!(m.attachments.unwrap()[0].content, "{}");
}

If you're using is_some or is_none (or, for Result, is_ok or is_err) in an if-else situation, that's usually an indication that you may want to rethink what you're doing, since that's not usually very idiomatic in Rust. Oftentimes, there's a helper method that does what you want in a simpler, easier to use way.

Upvotes: 6

Related Questions