0

I have a function that takes a closure to perform output-related logic (e.g. display to stdout):

fn handle(mut output: impl FnMut(String) -> ()) -> Result<(), String> {
    // do something that produces output string `message`
    let message = "example".to_string();
    Ok(output(message))
}

I'm trying to write an integration test for this function, where I define a stub output function, which saves the output string to local mutable variable:

#[test]
fn should_work() {
    let mut output_message = String::from("");
    let output = |message: String| {
        output_message = message;
    };

    let result = handle(output);

    assert!(result.is_ok());
    assert_eq!("blah", output_message);
}

However I have error:

error[E0502]: cannot borrow `output_message` as immutable because it is also borrowed as mutable
  --> src/lib.rs:18:24
   |
11 |     let output = |message: String| {
   |                  ----------------- mutable borrow occurs here
12 |         output_message = message;
   |         -------------- previous borrow occurs due to use of `output_message` in closure
...
18 |     assert_eq!("blah", output_message);
   |                        ^^^^^^^^^^^^^^ immutable borrow occurs here
19 | }
   | - mutable borrow ends here

Is there any way I can test using this approach? I briefly searched for some mock crates but all of the crates don't seem to be updated very often and they are a bit overkill for my scenario anyway.

If not, any better alternative to test this function?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Phuong Nguyen
  • 2,960
  • 1
  • 16
  • 25
  • 2
    For what it's worth, your code [works without any changes when enabling non-lexical lifetimes](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2015&gist=baab91bc01b0db3b36e849c314f6c53e). – Sven Marnach Nov 14 '18 at 19:16
  • @SvenMarnach interesting, I don't know about that – Phuong Nguyen Nov 14 '18 at 23:22

2 Answers2

3

You can put the anonymous function in a local scope, so that it is dropped by the time you assert:

#[test]
fn should_work() {
    let mut output_message = String::from("");

    let result = {
        let output = |message: String| {
            output_message = message;
        };

        handle(output)
    };

    assert!(result.is_ok());
    assert_eq!("blah", output_message);
}
Tarmil
  • 11,177
  • 30
  • 35
2

Inline the closure:

#[test]
fn should_work() {
    let mut output_message = String::from("");

    let result = handle(|message| {
        output_message = message;
    });

    assert!(result.is_ok());
    assert_eq!("blah", output_message);
}

This way, the closure is a temporary and doesn't borrow the value for a long time. As a bonus, you can avoid the type specification on the closure argument.

Or wait a few weeks until Rust 1.31 and turn on Rust 2018 mode, which has non-lexical lifetimes.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • thanks a lot. Coincidentally I also just saw your answer on sharing test utilities: https://stackoverflow.com/a/44545091/1273147 – Phuong Nguyen Nov 16 '18 at 01:10