How to run a loop without moving values?

I have a list of e-mail address in a Vec<String> and an email struct/trait that has a .to(addr) method, now I need to call to(addr) for every addr in Vec<String>, so I tried

pub async fn send(from: String, to: Vec<String>, subject: String, body: String, addr: SocketAddr, hello_name: String) {
  let email = Email::builder()
      .from(from)
      .subject(subject)
      .text(body);

  for t in to {
      email.to(t);
  }

But got this error

src/shutdown_mail.rs|14 col 9 error 382| use of moved value: `email`
||    |
|| 8  |     let email = Email::builder()
||    |         ----- move occurs because `email` has type `lettre_email::EmailBuilder`, which does not implement the `Copy` trait
|| ...
|| 14 |         email.to(t);
||    |         ^^^^^ value moved here, in previous iteration of loop
|| For more information about this error, try `rustc --explain E0382`.

and now I'm stuck :frowning:

Also, after finishing to add all tos I need to call let email = email.build().unwrap(); to finish to bulid the e-mail message.

I need to take ownership of to inside the loop but not email

I think your variable email needs to be mutable. So

  let mut email = Email::builder()
      .from(from)
      .subject(subject)
      .text(body);

This because you are mutating the email-variable in your loop after defining it.

And it that doesn't work, try

 for t in to {
      email.as_ref().unwrap().to(t);
  }

Hi ONiel, I thought that to but in fact .to takes Selfnot mut Self, here is the error with mut

src/shutdown_mail.rs|8 col 9 warning| variable does not need to be mutable
||   |
|| 8 |     let mut email = Email::builder()
||   |         ----^^^^^
||   |         |
||   |         help: remove this `mut`
||   |
||   = note: `#[warn(unused_mut)]` on by default
src/shutdown_mail.rs|14 col 9 error 382| use of moved value: `email`
||    |
|| 8  |     let mut email = Email::builder()
||    |         --------- move occurs because `email` has type `lettre_email::EmailBuilder`, which does not implement the `Copy` trait
|| ...
|| 14 |         email.to(t);
||    |         ^^^^^ value moved here, in previous iteration of loop
|| For more information about this error, try `rustc --explain E0382`.

There is one more thing (I will update the question next)

I need to call let email = email.build().unwrap();
after the loop. So I need to take onwership of every t in to but not of email :confused:

I will try as_ref

update: Sadly as_ref is not found for EmailBuilder

There must to be a better way,

Builder types seem tricky for things like this...

You could use fold, like so: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=8508d051ecb80655ecf2ee0f8733a162

So in your case:

pub async fn send(from: String, to: Vec<String>, subject: String, body: String, addr: SocketAddr, hello_name: String) {
  let email = Email::builder()
      .from(from)
      .subject(subject)
      .text(body);

  let email = to.into_iter().fold(email, |email, t| {
    email.to(t)
  });
}
1 Like

Actually, you don't even need fold. You can just do this:

pub async fn send(from: String, to: Vec<String>, subject: String, body: String, addr: SocketAddr, hello_name: String) {
  let mut email = Email::builder()
      .from(from)
      .subject(subject)
      .text(body);

  for t in to {
    email = email.to(t);
  }
}
1 Like

Hi Andrew, thank you so much it worked like a charm

I think that this was a bad API design, it would be much easier to supply a to_multiple or something like that that accepts a vector. Thank you again!!!

A last question: I don't really get why assigning inside loop did the trick, what magic meaning has that?

Regards

There's no magic meaning to it, you're just rebinding the email each iteration to ensure it is available for the next pass.

2 Likes

though it can seem magical until you get used to thinking about ownership. The to() method takes ownership of the email value, and returns it. unless you bind it again, it gets dropped when the method returns. so we explicitly bind it back to email.

note that we are rebinding the original email variable, which can live until the end of the send function. if, instead, we wrote let email = email.to(t); with the prepended let, we would be binding to a new email variable which goes out of scope at the end of each iteration of the loop.

2 Likes

Basically it's because they optimized the api for builder notation, e.g. this:

let mut email = Email::builder()
    .from(from)
    .subject(subject)
    .text(body);

But that means that the text function should return the Email object, which has the consequence you observed.

Thanks cliff, now everything make sense to me. I'm still getting used with ownership