Trying to understand why `'static` is required when making function generic

Hey there, I have run into a quite puzzling thing with lifetimes. I would be really glad if someone helped me understand why this works the way it works.

I have defined a function with the following signature, which is supposed to take a piece of data and turn into into another chunk of data that can be rendered to HTML. Post is basically the front matter and T can be another chunk of HTML:

pub fn post<'fm, 'md, 'post, T>(fm: &'fm Post, content: T) -> impl Renderable + 'post
    where
        'fm: 'post,
        'md: 'post,
        T: Renderable + 'md
{

And then I have another function that uses the previous function. This is supposed to grab a file, read it and then return a closure that lets you render HTML:

fn transform(meta: gen::Source) -> gen::Asset {
    let loc = meta.dirs.strip_prefix("content").unwrap();

    match meta.kind {
        gen::SourceKind::Index => match meta.ext.as_str() {
            "md" | "mdx" | "lhs" => {
                let loc = loc.join("index.html");

                let data = fs::read_to_string(&meta.path).unwrap();
                let (fm, md) = md::preflight::<md::Post>(&data);

                let call = move |assets: &[&gen::Asset]| {
                    let content  = md::render(&md);
                    html::post(&fm, Raw(content)).render().into()
                };

                gen::Asset {
                    kind: gen::AssetKind::Html(Box::new(call)),
                    out: loc,
                    meta,
                }
            },
            _ => gen::Asset {
                kind: gen::AssetKind::Unknown,
                out: loc.join(meta.path.file_name().unwrap()).to_owned(),
                meta,
            }
        },

But actually, I would like to make the function generic so I could use it for different kinds of html pages, depending on the data type, so I tried creating a trait like this, which is basically 1:1 the signature of the html::post function:

trait Transformable {
    fn transform<'f, 'm, 'html, T>(&'f self, content: T) -> String
        where
            'f: 'html,
            'm: 'html,
            T: Renderable + 'm;
}

impl Transformable for md::Post {
    fn transform<'f, 'm, 'html, T>(&'f self, content: T) -> String
        where
            'f: 'html,
            'm: 'html,
            T: Renderable + 'm {
        html::post(self, content)
            .render()
            .into()
    }
}

However when I try to use it like this:

fn transform<T>(meta: gen::Source) -> gen::Asset
    where
        T: for<'de> serde::Deserialize<'de>,
        T: Transformable,
{
    let loc = meta.dirs.strip_prefix("content").unwrap();

    match meta.kind {
        gen::SourceKind::Index => match meta.ext.as_str() {
            "md" | "mdx" | "lhs" => {
                let loc = loc.join("index.html");

                let data = fs::read_to_string(&meta.path).unwrap();
                let (fm, md) = md::preflight::<T>(&data);

                let call = move |assets: &[&gen::Asset]| {
                    let content  = md::render(&md);
                    T::transform(&fm, Raw(content)).into()
                };

                gen::Asset {
                    kind: gen::AssetKind::Html(Box::new(call)),
                    out: loc,
                    meta,
                }
            },
            _ => gen::Asset {
                kind: gen::AssetKind::Unknown,
                out: loc.join(meta.path.file_name().unwrap()).to_owned(),
                meta,
            }
        },

I get an error:
error

The Rust compiler tells me that to fix this I need to add 'static into that functions signature like so:

fn transform<T>(meta: gen::Source) -> gen::Asset
    where
        T: for<'de> serde::Deserialize<'de> + 'static,
        T: Transformable,

And indeed, after adding this it compiles, however I still don't know why I needed to add 'static there and what effect it has.

Maybe AssetKind::Html contains some Box<dyn Fn…(…) -> … + 'static>? (The + 'static is often implicit for such trait object types, so its definition might just read “Box<dyn Fn…(…) -> …>”.[1]) The call closure captures fm of type T, so the 'static bound needs to apply to the captured variable of type T, hence T: 'static is a requirement.

Feel free to share how AssedKind’s Html variant is defined so we can confirm this theory.


  1. If type inference “in my head” isn’t mistaken, I would thus expect it might be looking something like

    enum AssetKind {
        …
        Html(Box<dyn Fn(&[&gen::Asset]) -> String>),
        …
    }
    

    though it might also be FnMut or FnOnce for all I can tell. ↩︎

2 Likes

You're exactly right, because it's defined like this :slightly_smiling_face:

pub enum AssetKind {
    Html(Box<dyn Fn(&[&Asset]) -> String>),
    Bib(hayagriva::Library),
    Image,
    Unknown,
}

I still have a lot to learn about how traits, borrows and 'static work together, so thanks for
linking that guide!

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.