4

I am looking into the Iron source code for Response::with(), trying to understand how it applies a tuple as modifiers to the response.

As I understand, a modifier is simply a builder object, taking in a reference to the current context (self) and taking the object you wish to build on as a parameter (as long as you implement the modify function).

Assuming we have the following code:

use iron::modifiers::Header;

fn hello_world(_: &mut Request) -> IronResult<Response> {
    let string = get_file_as_string("./public/index.html");
    let content_type = Header(ContentType(Mime(TopLevel::Text, SubLevel::Html, vec![])));
    Ok(Response::with((status::Ok, string, content_type)))
}

Digging through the docs, I can see the implementation of Response::with() in Iron is as follows:

pub fn new() -> Response {
    Response {
        status: None, // Start with no response code.
        body: None, // Start with no body.
        headers: Headers::new(),
        extensions: TypeMap::new()
    }
}

/// Construct a Response with the specified modifier pre-applied.
pub fn with<M: Modifier<Response>>(m: M) -> Response {
    Response::new().set(m)
}

I'm struggling to see how my tuple of objects are translated into modifiers? I'd expect to see a foreach potentially iterating over each modifier, but here I simply see a set operation.

Could somebody explain the order to execution here and uncover what is actually happening?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Jacob Clark
  • 3,317
  • 5
  • 32
  • 61

1 Answers1

6

Interesting question! Let's look at the function signature again:

fn with<M: Modifier<Response>>(m: M) -> Response

This means that with accepts exactly one argument that implements Modifier<Response>. So next we could look up what types do implement the trait Modifier. In the documentation we see that it's not only implemented for String or Status, but for tuple types, too! Those implementations are written in this file. For example, let's look at this impl:

impl<X, M1, M2, M3> Modifier<X> for (M1, M2, M3)
    where M1: Modifier<X>,
          M2: Modifier<X>,
          M3: Modifier<X> 
{
    fn modify(self, x: &mut X) {
        self.0.modify(x);
        self.1.modify(x);
        self.2.modify(x);
    }
}

This implements the trait for every tuple of size 3 which elements also implement Modifier. And the implementation of modify is just to call the modify-implementation of each tuple element; this is the foreach you were searching for.

Lukas Kalbertodt
  • 79,749
  • 26
  • 255
  • 305
  • Thanks for the brilliant response, is there any reason why 'Modifier' implements tuples up to 5 and not beyond? – Jacob Clark May 24 '16 at 14:22
  • My guess is that it is just annoying to type too much trait implementations – aochagavia May 24 '16 at 14:35
  • 1
    @JacobClark Basically what aochagavia says. The problem is that Rust doesn't offer functionality to implement a trait for tuples of all sizes. So each implementation has to be written out manually (more or less) and adds more code to the project. So you have to stop at some point... Iron decided 5 is a good point to stop ;) – Lukas Kalbertodt May 24 '16 at 15:28