Ace of Spade
Ace of Spade

Reputation: 420

Why is mutable and extra scope needed

I want to print out the parameters passed to the FastCGI application:

extern crate fastcgi; // 1.0.0

use std::io::Write;

fn main() {
    fastcgi::run(|mut req| {
        let mut output = String::from("Content-Type: text/plain\n\n");

        {
            let mut params = req.params();

            loop {
                let param = params.next();

                match param {
                    Some(t) => output.push_str(&format!("{:?}", t)),
                    None => break,
                }
            }
        }

        write!(&mut req.stdout(), "{}", &output).unwrap_or(());
    });
}

My code works but I don't understand why I need to do it that way. My intuition is that I could write this non-working code:

extern crate fastcgi; // 1.0.0

use std::io::Write;

fn main() {
    fastcgi::run(|mut req| {
        let mut output = String::from("Content-Type: text/plain\n\n");
        let mut params = req.params();
        loop {
            let param = params.next();
            match param {
                Some(t) => output.push_str(&format!("{:?}", t)),
                None => break,
            }
        }
        write!(&mut req.stdout(), "{}", &output).unwrap_or(());
    });
}

Compiler Output:

error[E0502]: cannot borrow `req` as mutable because it is also borrowed as immutable
  --> src/main.rs:16:21
   |
8  |         let mut params = req.params();
   |                          --- immutable borrow occurs here
...
16 |         write!(&mut req.stdout(), "{}", &output).unwrap_or(());
   |                     ^^^^^^^^^^^^ mutable borrow occurs here
17 |     });
   |     - immutable borrow might be used here, when `params` is dropped and runs the destructor for type `std::boxed::Box<dyn std::iter::Iterator<Item = (std::string::String, std::string::String)>>`

I found the solution by Googling around and following suggestions of the compiler, but I don't understand why it is necessary.

Upvotes: 0

Views: 83

Answers (1)

rodrigo
rodrigo

Reputation: 98396

If you look at the definition of Request::params it is defined as:

pub fn params(&self) -> Params

that is deceptively simple, because Params has a hidden lifetime generic. It is actually desugared to:

pub fn params<'s>(&'s self) -> Params<'s>

That means that as long as the return of this function lives, self, that is the Request, is considered borrowed.

Similarly the definition of Request::stdout is desugared to:

pub fn stdout<'s>(&'s mut self) -> Stdout<'s>

This method borrows your object mutable, and as you probably know you cannot borrow the same object mutably and non-mutably at the same time.

Writing the extra scope limits the life of params and frees the borrow just before calling stdout so their lifetimes do not overlap and the code compiles.

If you do not like the extra indentation level you can also write:

drop(params);

just after the loop to finish the life of params manually.

You may think that params() and stdout() have nothing in common internally and that you should be able to use them mutably at the same time. You might be right, but the compiler does not care, it only sees how these functions are defined, not what they do internally.

Upvotes: 2

Related Questions