Search code examples
rust

Converting a trait to an owned implementation of itself?


I'm sorry the title is confusing and perhaps that is indication that my intended design is not ideal.

I'm working on a crate for JSON Schema. As part of my core crate, I have a trait that represents the output of the evaluation (named Report). In almost all cases, the Report borrows from the input serde_json::Value, except in the case of a compilation error. In that scenario, the Report needs to be owned.

Given that there's no way to indicate Self<'static>, I'm using an associated type, Owned, to fill in for the owned flavor, returned from the method into_owned:

pub trait Report<'v>: Clone + std::error::Error + Serialize + DeserializeOwned {
    type Error<'e>: 'e + Serialize + DeserializeOwned;
    type Annotation<'a>: 'a + Serialize + DeserializeOwned;
    type Output: 'static + Output;
    type Owned: 'static
        + Report<
            'static,
            Error<'static> = Self::Error<'static>,
            Annotation<'static> = Self::Annotation<'static>,
            Output = Self::Output,
        >;
    fn into_owned(self) -> Self::Owned;
   // snip
}

However, this doesn't work, as the compiler believes the referenced value is leaking:

    fn validate<'v>(
        &mut self,
        dialect_idx: usize,
        value: &'v Value,
    ) -> Result<(), CompileError<C, K>> {
        if !self.validate {
            return Ok(());
        }
        let mut evaluated = HashSet::default();
        let mut eval_numbers = Numbers::with_capacity(7);
        let key = self.dialects.get_by_index(dialect_idx).unwrap().schema_key;

        let report = self.schemas.evaluate(Evaluate {
            key,
            value,
            criterion: &self.criterion,
            output: <CriterionReportOutput<C, K>>::verbose(),
            instance_location: Pointer::default(),
            keyword_location: Pointer::default(),
            sources: self.sources,
            evaluated: &mut evaluated,
            global_numbers: self.numbers,
            eval_numbers: &mut eval_numbers,
        })?;
        if !report.is_valid() {
            let report: <C::Report<'v> as Report>::Owned = report.into_owned();
            return Err(CompileError::SchemaInvalid {
                report,
                backtrace: Backtrace::capture(),
            });
        }
        Ok(())
    }
}
error: lifetime may not live long enough
   --> grill-core/src/schema/compiler.rs:732:25
    |
707 |     fn validate<'v>(
    |                 -- lifetime `'v` defined here
...
732 |             let report: <C::Report<'v> as Report>::Owned = report.into_owned();
    |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type annotation requires that `'v` must outlive `'static`

I have a repro on the Playground but I'm not certain on it being completely 1-to-1. I'm sorry if that's not the case.

Is this possible? Is there anyway to accomplish this without re-designing everything? I doubt anyone wants to look through the source but its available on GitHub


Solution

  • The Rust forums user quinedot provided this answer (he opted not to post it here himself):

    You can put the equality constraint into Criterion instead.

    trait Criterion {
        type Owned: 'static + Report<'static>;
        type Report<'v>: Report<'v, Owned = <Self as Criterion>::Owned>;
    
    }
    
    struct CriterionImpl {} impl Criterion for CriterionImpl {
        type Owned = <ReportImpl<'static> as Report<'static>>::Owned;
        type Report<'v> = ReportImpl<'v>; 
    }
    
    struct Error<C: Criterion> {
        report: <C as Criterion>::Owned, 
    }