[Question] Why does this code cause a move?

I am looking at some older code from David Simmons (TUPM) where I don't understand why the compiler wants to move an object while I am just executing a method in the object. Yes, I am new at Rust, so I might be overlooking something obvious. The code lines are:

65     fn new(url: &str, http_username: &str, http_password: &str) -> Repository {
 66         // Create a new reqwest client.
 67         let client_builder = reqwest::ClientBuilder::new();
 68         client_builder.timeout(Duration::from_secs(TIMEOUT_SECS));
 69         let client = match client_builder.build() {

The messages from the compiler are:

 gbonnema  ~  projects  rust  tupm  cargo build
   Compiling tupm v0.1.0 (/home/data/gbonnema/projects/rust/tupm)
error[E0382]: use of moved value: `client_builder`
  --> src/upm/sync.rs:69:28
   |
68 |         client_builder.timeout(Duration::from_secs(TIMEOUT_SECS));
   |         -------------- value moved here
69 |         let client = match client_builder.build() {
   |                            ^^^^^^^^^^^^^^ value used here after move
   |
   = note: move occurs because `client_builder` has type `reqwest::ClientBuilder`, which does not implement the `Copy` trait

What I don't understand is why the compiler considers the execution of a method on the object equivalent to a move of the object.

Can anyone point me in the right direction?

P.S. I am reading the (paper version) of the community book (2nd edition) and have just started reading the o'reilly book (Programming rust).

The timeout method takes an owned self since a while ago: reqwest::ClientBuilder - Rust

This means that the value is moved into the function and promptly dropped, you're supposed to chain the methods.

1 Like

Thank you very much. The program is unsupported and just for exposition, not for use, so I guess I could've known.

Still, what I don't get is why chaining prevents dropping before use. In the example you get:

use std::time::Duration;

let client = reqwest::Client::builder()
    .gzip(true)
    .timeout(Duration::from_secs(10))
    .build()?;

So, if self is dropped at the end of timeout(), how can it execute build()?

timeout consumes self, but then returns it again at the end, which is what allows it to be chained.

In your original example, you call timeout (which consumes the value of client_builder), but then don't store/do anything with the return value, so it gets dropped. In your most recent example, you do use that return value, by immediately calling another method on it, so in that case it doesn't get dropped.

As an aside, this is why I tend to write builder method implementations using &mut self instead of self - it makes stuff like this a little nicer:

let mut builder = MyBuilder::new();

// consuming self
if (condition) {
    builder = builder.param(true);
}

// mutably referencing self
if (condition) {
    builder.param(true);
}

let result = builder.build();
2 Likes

Thanks, that explains a lot. I also get: study the code.

So there are different ways to implement chaining: moving ownership and borrowing.

Thank you for the explanation.

1 Like