1

I managed yet again to run into a lifetime issue that I seem to be unable to solve on my own.

I have this trait

pub trait MiddlewareHandler: Clone {
    fn invoke (&self, req: &Request, res: &mut Response) -> bool {
        true
    }

    // we need this because otherwise clone() would be ambiguous
    fn clone_box(&self) -> Box<MiddlewareHandler> { 
        box self.clone() as Box<MiddlewareHandler> 
    }
}

impl MiddlewareHandler for fn (req: &Request, res: &mut Response) -> bool {
    fn invoke(&self, req: &Request, res: &mut Response) -> bool{
        (*self).invoke(req, res)
    }
}

impl Clone for Box<MiddlewareHandler> {
    fn clone(&self) -> Box<MiddlewareHandler> { 
        self.clone_box() 
    }
}

That I implement for fn (req: &Request, res: &mut Response) -> bool in order to be able to use accept lightweight functions and more heavy weight MiddlewareHandler implementors at the same time.

I store them as Vec<Box<MiddlewareHandler>>

pub struct Middleware {
    handlers: Vec<Box<MiddlewareHandler>>
}

Now, the problem is, the compiler is yelling at me right here:

    pub fn add<T: MiddlewareHandler> (&mut self, handler: T) {
        self.handlers.push(box handler);
    }

It says:

error: value may contain references; add `'static` bound to `T`
       self.handlers.push(box handler);

The implementation should be pretty similar to the one used here:

https://github.com/iron/iron/blob/master/src/chain/stackchain.rs#L67

However, I seem to be unable to see the difference :-/

If anyone would like to give me a hand, I pushed the code to github into the static branch:

https://github.com/floor-org/floor/tree/static

Christoph
  • 26,519
  • 28
  • 95
  • 133

2 Answers2

1

The problem here is that, in order to safely create a boxed trait object, the original object cannot have any lifetime parameters (besides static), or the object itself also needs to respect that lifetime, which isn't possible in general. To fix it:

pub fn add<T: MiddlewareHandler + 'static> (&mut self, handler: T) {
    self.handlers.push(box handler);
}

Reads a bit weird, but it's saying "T needs to implement MiddlewareHandler and it cannot contain any references that do not have the static lifetime". This only works for static.

ember arlynx
  • 3,129
  • 2
  • 20
  • 22
  • I think I added `' static` to pretty much every place in this snippet but I obviously missed to try `+ 'static`there ;-) But one thing I don't understand is why it is not needed there: https://github.com/iron/iron/blob/master/src/chain/stackchain.rs#L67 How is their implementation different in that regard? – Christoph Jun 23 '14 at 22:55
  • Because the trait inherits from `Send`, and `Send` implies `'static` – ember arlynx Jun 23 '14 at 23:11
  • Damn it! I looked at the documentation for `Send` but it didn't ring a bell because I don't spawn any tasks here, so it seemed to be superfluous. Maybe you can answer two extra questions: `pub trait MiddlewareHandler: Clone + Send` works but it feels weird to "abuse" `Send` for that. Why can't I use `pub trait MiddlewareHandler: Clone + 'static` then? You said `Send` implies `'static` but I looked at the source of http://doc.rust-lang.org/core/kinds/trait.Send.html and can't find any hints about that. I would expect to see a `'static` there somewhere. – Christoph Jun 23 '14 at 23:23
  • 1
    The kinds are a bit weird, they are language builtins right now (though [there is an RFC to change that](https://github.com/rust-lang/rfcs/pull/127)). I would *expect* `Clone + 'static` to work, I'm sad that it doesn't. – ember arlynx Jun 24 '14 at 00:50
  • Ok, then. Would it be correct to derive from `send` if you really just want to make your type static atm? I want to write idiomatic Rust and deriving from `send` seems unnecessary given its description. Would you do the same here with the current design of Rust? – Christoph Jun 24 '14 at 05:47
1

A trait object erases the type of the internal data, meaning it is not known just by looking at the Box<Trait> trait object. In particular, any lifetimes of the data is erased too, so the compiler cannot tell if/when a trait object contains references to invalid data (i.e. lifetime of some reference has expired), hence Rust currently enforces that any data in a owned trait object must never "expire". That is, there are no references in it (well, to be precise that any references are valid forever, i.e. 'static).

This expressed via the built-in "trait" 'static:

pub fn add<T: 'static + MiddlewareHandler>(...

(This may change in future, with owned trait objects able to be bounded by real lifetimes, and so storing non-'static references would be safe.)

huon
  • 94,605
  • 21
  • 231
  • 225
  • Great answer! Thanks for that. I still wonder why this works for them: https://github.com/iron/iron/blob/master/src/chain/stackchain.rs#L67 I don't see how their code is different from mine. Also if I change the code like that I run into the next error `instantiating a type parameter with an incompatible type T, which does not fulfill 'static` So, this seems to be indicating that I have an error somewhere else maybe? :-S – Christoph Jun 23 '14 at 23:02