Can't get these lifetimes to work

I'm trying to make a borrow that I didn't expect to cause issues. No data is mutated whatsoever.

I've tried to strip down my code close to the minimum.
This is failing in the call to drawing::draw_figure. The lifetimes of figure and data_source are getting intricated, and the compiler is failing, despite none of the 2 objects is used after the draw_figure function.
Please help !

You're better off relying on polymorphism instead of trait "constructor"s based on lifetime parameters. For example, instead of Source<'a>, just make it Source. Then make Source::column polymorphic based on the lifetime associated with &self.

For example, the below compiles just fine:

mod data {
    use std::collections::HashMap;
    pub(crate) trait F64Column {
        fn iter(&self) -> Box<dyn Iterator<Item = Option<f64>> + '_>;
    }
    pub(crate) trait Column {
        fn f64(&self) -> Option<&(dyn F64Column + '_)>;
    }
    pub(crate) trait Source {
        fn column(&self, name: &str) -> Option<&(dyn Column + '_)>;
    }
    #[derive(Debug, Clone)]
    pub(crate) enum VecColumn {
        F64(Vec<f64>),
    }
    pub(crate) struct VecSource {
        columns: HashMap<String, Box<dyn Column + 'static>>,
    }
    impl VecSource {
        pub(crate) fn new() -> Self {
            Self {
                columns: HashMap::new(),
            }
        }
        pub(crate) fn with_f64_column(mut self, name: &str, col: Vec<f64>) -> Self {
            drop(
                self.columns
                    .insert(name.to_owned(), Box::new(VecColumn::F64(col))),
            );
            self
        }
    }
    impl F64Column for Vec<f64> {
        fn iter(&self) -> Box<dyn Iterator<Item = Option<f64>> + '_> {
            Box::new(
                self.as_slice()
                    .iter()
                    .copied()
                    .map(|f| f.is_finite().then_some(f)),
            )
        }
    }
    impl Column for VecColumn {
        fn f64(&self) -> Option<&(dyn F64Column + '_)> {
            match *self {
                Self::F64(ref v) => Some(v),
            }
        }
    }
    impl Source for VecSource {
        fn column(&self, name: &str) -> Option<&(dyn Column + '_)> {
            self.columns.get(name).map(|c| &**c)
        }
    }
}
mod ir {
    use crate::data::VecColumn;
    pub(crate) enum DataCol {
        Inline(VecColumn),
        SrcRef(String),
    }
    pub(crate) struct Figure {
        pub x_data: DataCol,
        pub y_data: DataCol,
    }
}
mod drawing {
    use crate::data::{Column, Source};
    use crate::ir::{DataCol, Figure};
    fn get_column<'a, D: Source>(col: &'a DataCol, data_source: &'a D) -> &'a dyn Column {
        match *col {
            DataCol::Inline(ref c) => c,
            DataCol::SrcRef(ref name) => data_source.column(name).expect("Column not found"),
        }
    }
    pub(crate) fn draw_figure<D: Source>(figure: &Figure, data_source: &D) {
        let x_data = get_column(&figure.x_data, data_source);
        let y_data = get_column(&figure.y_data, data_source);
        let x_col = x_data.f64().expect("x_data is not f64");
        let y_col = y_data.f64().expect("y_data is not f64");
        println!("Drawing figure");
        for (x, y) in x_col.iter().zip(y_col.iter()) {
            println!("    ({}, {})", x.unwrap(), y.unwrap());
        }
    }
}
use data::{VecColumn, VecSource};
use ir::{DataCol, Figure};
fn main() {
    let x: Vec<f64> = (0u8..10).map(f64::from).collect();
    let y: Vec<f64> = x.iter().map(|a| a * a).collect();
    let source = VecSource::new().with_f64_column("x", x);
    let figure = Figure {
        x_data: DataCol::SrcRef("x".to_owned()),
        y_data: DataCol::Inline(VecColumn::F64(y)),
    };
    drawing::draw_figure(&figure, &source);
}

In Rust, lifetimes aren't allowed to be dangling, even theoretically, even temporarily (except arcane details of std's containers that don't apply here).

This implies that when you have Trait<'a> it means that everything marked by 'a must have been created first, before self has been created, and must be kept alive for longer than self can exist.

When two things use the same 'a is creates a more restrictive requirement that there must be a common 'a that satisfies all of the requirements of all of the things sharing this lifetime, all it must be valid for all of them at the same time (all the worst cases together).

This can create impossible to satisfy circular dependencies when nothing can be dropped before anything else, because everything has been marked as staying alive for just as long.

Ultimately you have a design error here from placing lifetimes on the traits.

That syntax on the trait refers to some bigger scope of some hypothetical external data that certainly is not self, and can never be even allowed to be stored in self, since by definition it has to always outlive instances of self.

Then you make &'a self loan forced to be equal to that other pre-existing loan of something else that is definitely not self, and it creates restrictions that don't make sense – self implements trait for lifetime that must never be shorter than self (since self could end up using something dangling), while also promises you can borrow self for exactly that lifetime, one that also must never be longer than self (since that's a dangling pointer). Rust can sometimes solve this paradox by picking lifetimes that are exactly equal, but can't when traits and all those weaved-together relationships require these to be exactly equal to whole lifetimes of two different objects that will be created and dropped one after another.

Just get rid of the lifetimes on the traits. You've probably meant fn method<'a>(&'a self) which names the loan of self created when the method is called. Usually this lifetime isn't written explicitly, because the defaults just work, and you give names to everything else instead.

1 Like

Thanks a lot for the quick and detailed answers

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.