Zero-copy deserialization in generic function

⚓ rust    📅 2025-05-11    👤 surdeus    👁️ 5      

surdeus

Warning

This post was published 50 days ago. The information described in this article may have changed.

I'm trying to deserialize a generic type T in a function without requiring that T: DeserializeOwned, but just that T: Deserialize, i.e. I would like to permit it to borrow from the input. However I don't see a way to make this work. Consider this code:

use std::fmt;

use serde::Deserialize;

#[derive(Debug, Deserialize)]
struct Foo<T> {
    #[allow(dead_code)]
    bar: T,
}

fn main() {
    deserialize_and_print();
    deserialize_generic_and_print::<Foo<&str>>();
}

fn deserialize_and_print() {
    let s = r#"{ bar: "baz" }"#.to_string();
    let foo: Foo<&str> = serde_json::from_str(&s).unwrap();
    println!("{foo:?}");
}

fn deserialize_generic_and_print<T: fmt::Debug + Deserialize>() {
    let s = r#"{ bar: "baz" }"#.to_string();
    let foo: Foo<T> = serde_json::from_str(&s).unwrap();
    println!("{foo:?}");
}

The non-generic function deserialize_and_print works perfectly fine, but this code does not compile because the Deserialize bound in deserialize_generic_and_print is missing a lifetime.

But how can I fill out this lifetime? I don't want the caller to choose a lifetime and it certainly can't just be any lifetime. The way I think about it, I want to specify the lifetime of a local variable of the function, i.e. the lifetime of s. But I'm not aware of any way to do that. Conceptually I want to write this basically:

// Note the lack of a generic lifetime parameter.
fn deserialize_generic_and_print<T: fmt::Debug + Deserialize<'s>>() {
    's: {
        let s = r#"{ bar: "qux" }"#.to_string();
        let foo: Foo<T> = serde_json::from_str(&s).unwrap();
        println!("{foo:?}");
    }
}

If I try to just fill in a generic lifetime like this...

fn deserialize_generic_and_print<'s, T: fmt::Debug + Deserialize<'s>>() {
    let s = r#"{ bar: "qux" }"#.to_string();
    let foo: Foo<T> = serde_json::from_str(&s).unwrap();
    println!("{foo:?}");
}

I get borrowed value does not live long enough and s dropped here [at the end of the function] while still borrowed, because the caller could in principle choose a lifetime that is longer than the lifetime of s - but is there no way to restrict the caller to not allow any lifetime longer than a local variable of the function?

I'd like to know whether I am running up against some kind of misunderstanding or fundamental limitation of the borrow checker, or whether the issue is a lack of a way to specify that "local variable lifetime".

1 post - 1 participant

Read full topic

🏷️ rust_feed