A trouble implementing the observer pattern for functions

#1

Hello. I’m struggling with this code.

type Result<T> = std::result::Result<T, &'static str>;

struct Data {
    pub num: i32,
    pub children: Vec<Data>,
}

trait FromData<'a>
where
    Self: Sized + 'a,
{
    fn from_data(d: &'a Data) -> Result<Self>;
}

trait Handler {
    fn handle(&self, d: &Data) -> Result<()>;
}

struct ParsingHandler<F, P>
where
    F: Fn(P) -> Result<()>,
    P: FromData<'_>,
{
    callback: F,
    parsed_into: std::marker::PhantomData<P>,
}

impl<F, P> Handler for ParsingHandler<F, P>
where
    F: Fn(P) -> Result<()>,
    P: FromData<'_>,
{
    fn handle(&self, d: &Data) -> Result<()> {
        match P::from_data(d) {
            Ok(parsed) => (self.callback)(parsed),
            Err(s) => Err(s),
        }
    }
}

struct Parsed<'a>(&'a Data, &'a Data);

impl<'a> FromData<'a> for Parsed<'a> {
    fn from_data(d: &'a Data) -> Result<Self> {
        if d.num < 2 {
            Err("d.num < 2")
        } else {
            Ok(Parsed(&d.children[0], &d.children[1]))
        }
    }
}

fn test<'a>(parsed: Parsed<'a>) -> Result<()> {
    println!("{} {}", parsed.0.num, parsed.1.num);
    Ok(())
}

fn main() {
    let a: Vec<Box<dyn Handler>> = vec![Box::new(
        ParsingHandler {
            callback: test,
            parsed_into: std::marker::PhantomData::<Parsed>,
        }
    )];
    
    let data = Data {
        num: 2,
        children: vec![
            Data { num: 0, children: vec![], },
            Data { num: 0, children: vec![], },
        ],
    };
    
    a[0].handle(&data);
}

Playground

I have a trait Handler with a required method handle(&self, &Data), and I want to implement it for regular functions, too. Moreover, if the function takes a type P other than &Data, and P implements FromData<'a>, a trait that converts &Data to P, I want to perform the conversion and pass the result to the function. Since I can’t make an implementation generic over a function parameter, I’ve decided to make a wrapper struct — in the code linked above, it’s the ParsingHandler.

But I have a trouble specifying the lifetime parameter for the FromData bound (marked as '_ in the code). It comes from the type of the function’s formal argument. In the implementation of the trait Handler for the ParsingHandler, I want this lifetime to be the same as the lifetime of &Data passed to the method.

In addition, I need to store the handlers somehow. For this reason I have a Vec<dyn Handler> in the code.

How should the code be modified so that it compiles?

#2

Hm, I’m not sure. Lifetimes can be created with for<'b> syntax, but that’s not enough in this case:

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=6aae19eb7165ac70a22340f51d12dad9

Another approach could be to remove lifetimes from FromData completely, and use:

trait FromData<D> {
   type Output;
   fn from(data: D) -> Result<Self::Output> {}
}

and then connect lifetime of D an Output later in the implementation (D can be &Data).

2 Likes
#3

Thank you for your answer. I’ve spent some time trying to apply the approach you suggested to the code, and I’ve (almost) made the program compile.

type Result<T> = std::result::Result<T, &'static str>;

struct Data {
    pub num: i32,
    pub children: Vec<Data>,
}

trait FromData<T>
where
    Self: Sized,
{
    type Out;
    fn from_data(d: T) -> Result<Self::Out>;
}

trait Handler<D> {
    fn handle(&self, d: D) -> Result<()>;
}

struct ParsingHandler<F, P, D>
where
    F: Fn(P) -> Result<()>,
    P: FromData<D, Out = P>,
{
    callback: F,
    parsed_into: std::marker::PhantomData<P>,
    data: std::marker::PhantomData<D>,
}

impl<F, P, D> Handler<D> for ParsingHandler<F, P, D>
where
    F: Fn(P) -> Result<()>,
    P: FromData<D, Out = P>,
{
    fn handle(&self, d: D) -> Result<()> {
        match P::from_data(d) {
            Ok(parsed) => (self.callback)(parsed),
            Err(s) => Err(s),
        }
    }
}

struct Parsed<'a>(&'a Data, &'a Data);

impl<'a> FromData<&'a Data> for Parsed<'a> {
    type Out = Parsed<'a>;

    fn from_data(d: &'a Data) -> Result<Self::Out> {
        if d.num < 2 {
            Err("d.num < 2")
        } else {
            Ok(Parsed(&d.children[0], &d.children[1]))
        }
    }
}

fn test<'a>(parsed: Parsed<'a>) -> Result<()> {
    println!("{} {}", parsed.0.num, parsed.1.num);
    Ok(())
}

fn main() {
    let a: Vec<Box<dyn Handler<&Data>>> = vec![Box::new(
        ParsingHandler {
            callback: test,
            parsed_into: std::marker::PhantomData::<Parsed>,
            data: std::marker::PhantomData::<&Data>,
        }
    )];
    
    let data = Data {
        num: 2,
        children: vec![
            Data { num: 0, children: vec![], },
            Data { num: 0, children: vec![], },
        ],
    };
    
    a[0].handle(&data);
}

Playground

Unfortunately, I couldn’t make it work the way I intended: the function’s lifetime parameter binds the whole ParsingHandler object now. This requires that the data instance be created before the ParsingHandler object. Otherwise, as in the code, the compiler complains that the Vec's Drop implementation may use the dropped data instance.

The data instance represents an event created during the execution of a program. The whole pattern is effectively rendered useless if the handlers have to be created after the events.

How can I remove this restriction from ParsingHandler?