4

Like in a previous question of mine, I'm refactoring a small Rust project. Also similar to that question, the compiler and I don't see eye to eye on methods and values. The error message is very different, though, and this seems to potentially involve libraries, hyper in this example.

/* extern and use directives... */

pub struct WebServer /* with stuff not relevant to the error */;

impl WebServer {
    pub fn serve(&mut self) {
        let server = Server::http(Ipv4Addr(127, 0, 0, 1), 80);
        let mut listening = server.listen(self.return_page).unwrap();
        listening.await();
    }

    fn return_page(&mut self, req: Request, mut res: Response) {
        /* Put the page together */
    }
}

When serve() and return_page() are just functions in main.rs, this compiles and runs.

Tucked into a struct as above, though, I get the following error:

.../src/main.rs:13:48: 13:59 error: attempted to take value of method `return_page` on type `&mut WebServer`
.../src/main.rs:13         let mut listening = server.listen(self.return_page).unwrap();
                                                                  ^~~~~~~~~~~
.../src/main.rs:13:48: 13:59 help: maybe a `()` to call it is missing? If not, try an anonymous function
.../src/main.rs:13         let mut listening = server.listen(self.return_page).unwrap();
                                                                  ^~~~~~~~~~~

I assume that the parser just needs a nudge in the right direction, but even with the advice I was given on the related problem, I don't see anything in the documentation suggesting a way to say, "no, I really mean the function as a parameter, just like invoked function's signature says I need." What am I missing, given that the above syntax works outside of a struct?

A half-hearted attempt at a closure seemed to be worse and the error message still suggested calling return_page instead of passing it, but I may well be misunderstanding the usage in this case.

Edit: To clarify the situation after working with the existing answers, the original return_page() doesn't have a self parameter, of course, which is itself part of the problem. But it also needs self inside the object, because it needs access to member variables.

Community
  • 1
  • 1
John C
  • 1,931
  • 1
  • 22
  • 34

2 Answers2

4

You can't pass return_page directly to listen, because Handler is only implemented on functions that take 2 parameters, a request and a response. return_page takes 3 parameters (self counts as a parameter).

Try this:

let mut listening = server.listen(|&: request, response| self.return_page(request, response)).unwrap();

Alternatively, add an impl for Handler and move your return_page function to this impl, named handle.

impl WebServer {
    pub fn serve(&mut self) {
        let server = Server::http(Ipv4Addr(127, 0, 0, 1), 80);
        let mut listening = server.listen(&self).unwrap();
        listening.await();
    }
}

impl<'a> Handler for &'a WebServer {
    fn handle(&self, req: Request, mut res: Response) {
        /* Put the page together */
    }
}

Note that Handler passes self by immutable reference, not by mutable reference. You may need to use RefCell to bypass this.

Francis Gagné
  • 60,274
  • 7
  • 180
  • 155
  • 1
    The `self` parameter stupidly snuck by me, yes, thanks. The closure syntax throws an error that `for<'r,'r>` (with the function's type) isn't implemented for `closure`. Implementing `Handler` gives the same error or (of course) tells me `return_page` doesn't exist (which it doesn't). Am I missing something? – John C Jan 17 '15 at 22:13
  • 1
    See my edits. I changed the closure's type, changed the argument to `listen` and changed the `impl Handler` to be on a reference, otherwise I'd have to move `self` into `listen`, which wouldn't be allowed. – Francis Gagné Jan 17 '15 at 22:45
  • Oh, _obviously_ it should've been `&self`, right! Any idea why `&WebServer` would throw `missing lifetime specifier [E0106]`? I've only see that in relation to functions. (The closure change doesn't seem to change the result, though.) – John C Jan 17 '15 at 23:33
1

The short answer is that you can use UFCS (Uniform Function Call Syntax):

let mut listening = server.listen(WebServer::return_page).unwrap();

The long answer is that I doubt this will make your code work. You did not show how return_page was defined directly in main, but I guess it was something like fn return_page(req: Request, mut res: Response).

This signature is different from the return_page defined as a method of WebServer. Let's take a look at it:

fn return_page(&mut self, req: Request, mut res: Response)

this is a function that takes a first argument of type &mut self, i.e. &mut WebServer. If listen was not expecting this signature you will get a "mismatched types" error.

Let me try creating a mock implementation of your code:

// serve calls listen passing it return_page. Note that
// the signature of return_page matches the one that
// listen is expecting, fn(u32, u32)
fn serve() {
    listen(return_page)
}

// mock return_page
fn return_page(a: u32, b: u32) { println!("{}, {}", a, b) }

// listen expects a function that takes two u32
// and returns nothing
fn listen( f: fn(u32, u32) ) { f(1, 2) }

fn main() {
    serve();
}

playpen

Now let's move serve and return_page into an impl, as I think you did:

fn listen( f: fn(u32, u32) ) { f(1, 2) }

struct WebServer;
impl WebServer {
    fn serve(&self) {
        listen(WebServer::return_page)
    }
    
    // a method is a function that takes the implementing type
    // as a first parameter. We can use UFCS to invoke it as 
    // a function: WebServer::return_page
    fn return_page(&self, a: u32, b: u32) { println!("{}, {}", a, b) }
}

playpen

Oops, mismatched types: listen is not expecting a first parameter of type &WebServer. You would need this signature: fn listen(f: fn(&WebServer, u32, u32) )

Note that if hypothetically listen did have that signature, our program would work. See this last playpen example

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
Paolo Falabella
  • 24,914
  • 3
  • 72
  • 86
  • That definitely explains the problem. Unfortunately, I'm not about to tinker with `listen`, since it's part of the `hyper` library. And `return_page` needs access to member variables, so that's constrained, too. – John C Jan 17 '15 at 22:04