Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you create a generic function in Rust with a trait requiring a lifetime?

I am trying to write a trait which works with a database and represents something which can be stored. To do this, the trait inherits from others, which includes the serde::Deserialize trait.

trait Storable<'de>: Serialize + Deserialize<'de> {
    fn global_id() -> &'static [u8];
    fn instance_id(&self) -> Vec<u8>;
}

struct Example {
    a: u8,
    b: u8
}

impl<'de> Storable<'de> for Example {
    fn global_id() -> &'static [u8] { b"p" }
    fn instance_id(&self) -> Vec<u8> { vec![self.a, self.b] }
}

Next, I am trying to write this data using a generic function:

pub fn put<'de, S: Storable>(&mut self, obj: &'de S) -> Result<(), String> {
    ...
    let value = bincode::serialize(obj, bincode::Infinite);
    ...
    db.put(key, value).map_err(|e| e.to_string())
}

However, I am getting the following error:

error[E0106]: missing lifetime specifier
--> src/database.rs:180:24
    |
180 |     pub fn put<'de, S: Storable>(&mut self, obj: &'de S) -> Result<(), String> {
    |                        ^^^^^^^^ expected lifetime parameter

Minimal example on the playground.

How would I resolve this, possibly avoid it altogether?

like image 782
Eadword Avatar asked Jan 30 '23 10:01

Eadword


2 Answers

You have defined Storable with a generic parameter, in this case a lifetime. That means that the generic parameter has to be propagated throughout the entire application:

fn put<'de, S: Storable<'de>>(obj: &'de S) -> Result<(), String> { /* ... */ }

You can also decide to make the generic specific. That can be done with a concrete type or lifetime (e.g. 'static), or by putting it behind a trait object.

Serde also has a comprehensive page about deserializer lifetimes. It mentions that you can choose to use DeserializeOwned as well.

trait Storable: Serialize + DeserializeOwned { /* ... */ }

You can use the same concept as DeserializeOwned for your own trait as well:

trait StorableOwned: for<'de> Storable<'de> { }

fn put<'de, S: StorableOwned>(obj: &'de S) -> Result<(), String> {
like image 174
Shepmaster Avatar answered Feb 03 '23 14:02

Shepmaster


You have the 'de lifetime in the wrong place -- you need it to specify the argument to Storable, not the lifetime of the reference obj.

Instead of

fn to_json<'de, S: Storable>(obj: &'de S) -> String {

use

fn to_json<'de, S: Storable<'de>>(obj: &S) -> String {

Playground.

The lifetime of obj doesn't actually matter here, because you're not returning any values derived from it. All you need to prove is that S implements Storable<'de> for some lifetime 'de.

If you want to eliminate the 'de altogether, you should use DeserializeOwned, as the other answer describes.

like image 31
trent Avatar answered Feb 03 '23 13:02

trent