-2

I do not know Rust well enough to understand lifetimes and closures yet...

Trying to collect the downloaded data into a vector using tokio-curl:

extern crate curl;
extern crate futures;
extern crate tokio_core;
extern crate tokio_curl;

use std::io::{self, Write};
use std::str;

use curl::easy::Easy;
use tokio_core::reactor::Core;
use tokio_curl::Session;

fn main() {
    // Create an event loop that we'll run on, as well as an HTTP `Session`
    // which we'll be routing all requests through.
    let mut lp = Core::new().unwrap();
    let mut out = Vec::new();
    let session = Session::new(lp.handle());

    // Prepare the HTTP request to be sent.
    let mut req = Easy::new();
    req.get(true).unwrap();
    req.url("https://www.rust-lang.org").unwrap();
    req.write_function(|data| {
            out.extend_from_slice(data);
            io::stdout().write_all(data).unwrap();
            Ok(data.len())
        })
        .unwrap();

    // Once we've got our session, issue an HTTP request to download the
    // rust-lang home page
    let request = session.perform(req);

    // Execute the request, and print the response code as well as the error
    // that happened (if any).
    let mut req = lp.run(request).unwrap();
    println!("{:?}", req.response_code());
    println!("out: {}", str::from_utf8(&out).unwrap());
} 

Produces an error:

error[E0373]: closure may outlive the current function, but it borrows `out`, which is owned by the current function
  --> src/main.rs:25:24
   |
25 |     req.write_function(|data| {
   |                        ^^^^^^ may outlive borrowed value `out`
26 |             out.extend_from_slice(data);
   |             --- `out` is borrowed here
   |
help: to force the closure to take ownership of `out` (and any other referenced variables), use the `move` keyword, as shown:
   |     req.write_function(move |data| {

Investigating further, I see that Easy::write_function requires the 'static lifetime, but the example of how to collect output from the curl-rust docs uses Transfer::write_function instead:

use curl::easy::Easy;

let mut data = Vec::new();
let mut handle = Easy::new();
handle.url("https://www.rust-lang.org/").unwrap();
{
    let mut transfer = handle.transfer();
    transfer.write_function(|new_data| {
        data.extend_from_slice(new_data);
        Ok(new_data.len())
    }).unwrap();
    transfer.perform().unwrap();
}
println!("{:?}", data);

The Transfer::write_function does not require the 'static lifetime:

impl<'easy, 'data> Transfer<'easy, 'data> {
    /// Same as `Easy::write_function`, just takes a non `'static` lifetime
    /// corresponding to the lifetime of this transfer.
    pub fn write_function<F>(&mut self, f: F) -> Result<(), Error>
        where F: FnMut(&[u8]) -> Result<usize, WriteError> + 'data
    {
...

But I can't use a Transfer instance on tokio-curl's Session::perform because it requires the Easy type:

pub fn perform(&self, handle: Easy) -> Perform {

transfer.easy is a private field that is directly passed to session.perform.

It this an issue with tokio-curl? Maybe it should mark the transfer.easy field as public or implement new function like perform_transfer? Is there another way to collect output using tokio-curl per transfer?

ljedrz
  • 20,316
  • 4
  • 69
  • 97
estin
  • 3,051
  • 1
  • 24
  • 31
  • 1
    What have you *tried*? You are [expected to show effort](http://meta.stackoverflow.com/q/261592/155423), not just "this doesn't work, fix it for me". There are [other questions with the same error](http://stackoverflow.com/search?q=%5Brust%5D+%22closure+may+outlive+the+current+function%22), and [search engines have more](https://www.google.com/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=site%3Astackoverflow.com%20rust%20%22closure%20may%20outlive%20the%20current%20function%22). There's even a **help** line in the error message that suggests something to try. – Shepmaster Sep 13 '16 at 15:10
  • 1
    Note that tokio (and other async libraries) are *bleeding edge* and may not currently be the best first choice to learn about Rust. – Shepmaster Sep 13 '16 at 15:14
  • Yep. I was try many stupid ways to solve this, even define static Vec... but without luck. And I do not publish my wrong tries here, because I do not understand many things yet. async stuff do not disturb learning process of new language if student already works with async stuff on other languages IMHO Thanks any way – estin Sep 13 '16 at 21:39

1 Answers1

2

The first thing you have to understand when using the futures library is that you don't have any control over what thread the code is going to run on.

In addition, the documentation for curl's Easy::write_function says:

Note that the lifetime bound on this function is 'static, but that is often too restrictive. To use stack data consider calling the transfer method and then using write_function to configure a callback that can reference stack-local data.

The most straight-forward solution is to use some type of locking primitive to ensure that only one thread at a time may have access to the vector. You also have to share ownership of the vector between the main thread and the closure:

use std::sync::Mutex;
use std::sync::Arc;

let out = Arc::new(Mutex::new(Vec::new()));
let out_closure = out.clone();

// ...

req.write_function(move |data| {
    let mut out = out_closure.lock().expect("Unable to lock output");
    // ...
}).expect("Cannot set writing function");

// ...

let out = out.lock().expect("Unable to lock output");
println!("out: {}", str::from_utf8(&out).expect("Data was not UTF-8"));

Unfortunately, the tokio-curl library does not currently support using the Transfer type that would allow for stack-based data.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 1
    After typing all of this up, I see that [you already asked the maintainer of tokio-curl and got the same response](https://github.com/tokio-rs/tokio-curl/issues/3). It's a shame you didn't decide to share what you learned with the rest of us. – Shepmaster Sep 16 '16 at 13:45
  • I do not think so. Yes, I have learned the lesson, but I have other questions corresponding to `futures-rs`, which I want to resolve before publish the result, a specially after down votes on my question ( And now I have this clearest answer. Thanks! – estin Sep 16 '16 at 14:24