Search code examples
rustlifetimebevy

Can't Deserialize in generic trait function due to lifetime issue, but it works when out of generic function


I would like to implement a trait function that takes a path and gives an owned value.

use bevy::prelude::*;
use serde::de;

pub trait RonResource<'a, T: de::Deserialize<'a> + Resource> {
    fn import_from_ron(path: &str) -> T {

        let ron_string = std::fs::read_to_string(path).unwrap();

        ron::from_str::<T>(
                &ron_string
            ).expect(("Failed to load: ".to_owned() + path).as_str())
    }
}

I implement the 'a lifetime because de::Deserialize needs it. But the compiled tells me that "ron_string" will be dropped while still borrowed. (at the "&ron_string" line)

This code works fine if I implement it without generics, like this for exemple:

let settings = ron::from_str::<GasParticleSystemSettings>(
            &std::fs::read_to_string("assets/settings/gas_ps_settings.ron").unwrap()
        ).expect("Failed to load settings/gas_ps_settings.ron")
        .side_gas;

I don't get why the value need to "survive" the whole function as it will not be needed. Otherwise, the specific code wouldn't work!


Solution

  • The problem is when using Deserialize<'a> the deserialized object might contain references to the original str ie something like this:

    struct HasRef<'a> {
        s: &'a str,
    }
    

    is allowed. It would contain references to ron_string which is dropped at the end of the function.

    What's totally save though is to just require DeserializeOwned instead at which point you can't do 0-copy deserialization any more but you don't have to keep the original string around either:

    pub trait RonResource<T: de::DeserializeOwned + Resource> {
        fn import_from_ron(path: &str) -> T {
            let ron_string = std::fs::read_to_string(path).unwrap();
    
            ron::from_str::<T>(
                    &ron_string
                ).expect(("Failed to load: ".to_owned() + path).as_str())
        }
    }
    

    Your second example works presumably because you only access GasParticleSystemSettings.side_gas which I assume either does not contain references or is straight up Copy neither of which would create a lifetime issue.