valuable icon indicating copy to clipboard operation
valuable copied to clipboard

Introduce a lifetime generic to `Visit` in order to allow borrowing out specific parts of the `valuable::Value` during visits

Open nagisa opened this issue 2 years ago • 1 comments

Motivating use-case

My motivating use-case is to write down in tracing 0.2 a visitor that is able to borrow Event’s message. Doing so today is impossible even though lifetimes should conceptually mostly work out. This problem extends to valuable::Value and its Visit trait as well, so the remaining of the issue will focus on valuable.

So lets say we begin by writing a function as such (any mistakes mine, I basically sketched this out in a github comment field):

fn extract_message<'val>(value: &'val valuable::Value<'val>) -> &'val str {
    struct Visitor<'val> {
        message: &'val str
    }
    impl<'val> valuable::Visit for Visitor<'val> {
        fn visit_entry(&mut self, key: valuable::Value<'_>, value: valuable::Value<'_>) {
            match (key, value) {
                (valuable::Value::String("message"), valuable::Value::String(v)) => {
                    self.message = v; // ERROR: `'_` does not outlive `'val`.
                }
                _ => {}
            }
        }
    }

    let mut visitor = Visitor<'val> { message: "" };
    value.visit(&mut visitor); 
    visitor.message    
}

Rust today provides no way to tell that, '_ will outlive 'val here, and so users are forced to allocate in the implementation to extract the string data here.

Proposed solution

The proposed solution here is to introduce a lifetime to Visit such that Visit becomes like this:

pub trait Visit<'a> {
    fn visit_value(&mut self, value: Value<'a>);
    fn visit_named_fields(&mut self, named_values: &NamedValues<'a>) { ... }
    fn visit_unnamed_fields(&mut self, values: &[Value<'a>]) { ... }
    fn visit_primitive_slice(&mut self, slice: Slice<'a>) { ... }
    fn visit_entry(&mut self, key: Value<'a>, value: Value<'a>) { ... }
}

This gives implementers an optional opportunity to tie the lifetime of values to the lifetime of the visitors contents. Any implementors wanting the current behaviour can write the implementation as such:

// Unfortunately a breaking change ­– you cannot leave out the `<'_>`.
impl Visit<'_> for SomeVisitor { ... }

While implementations such as one presented in the motivating use-case above are also made possible:

impl<'val> valuable::Visit<'val> for Visitor<'val> {
    fn visit_entry(&mut self, key: valuable::Value<'val>, value: valuable::Value<'val>) {
        match (key, value) {
            (valuable::Value::String("message"), valuable::Value::String(v)) => {
                self.message = v; // A-OK!
            }
            _ => {}
        }
    }
}

nagisa avatar Jun 11 '22 17:06 nagisa