Generic Default intialization


#1

I want to have a function inside generic which can have Default member like this:

use std::default::Default;
use serde::Serialize;

#[derive(Debug, Serialize, Deserialize)]
pub struct DefaultBody {
    pub content: String,
}

impl Default for DefaultBody {
    fn default() -> DefaultBody {
        DefaultBody {
            content: "Insert content here...".to_string()
        }
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct DefaultHeader {
    pub title: String,
}

impl Default for DefaultHeader {
    fn default() -> DefaultHeader {
         DefaultHeader {
             title: "Insert header here...".to_string(),
         }
    }
}

pub type DefaultViewModel<'h, 'b> = ViewModel<'h, 'b, DefaultHeader, DefaultBody>;
pub type DefaultHeaderViewModel<'h, 'b, B> = ViewModel<'h, 'b, DefaultHeader, B>;
pub type DefaultBodyViewModel<'h, 'b, H> = ViewModel<'h, 'b, H, DefaultBody>;

#[derive(Debug, Serialize)]
pub struct ViewModel<'h, 'b, H: 'h, B: 'b>
where
    H: Serialize + 'h,
    B: Serialize + 'b,
{
    pub header: &'h H,
    pub body: &'b B,
}

impl<'h, 'b, H, B> ViewModel<'h, 'b, H, B>
where
    H: Serialize + 'h,
    B: Serialize + 'b,
{
    pub fn new(header: &'h H, body: &'b B) -> ViewModel<'h, 'b, H, B> {
        ViewModel { header, body }
    }

    pub fn with_default_header(body: &'b B) -> DefaultHeaderViewModel<B> {
        let h: DefaultHeader = Default::default();
        ViewModel {
            header: &h,
            body,
        }
    }

    pub fn with_default_body(header: &'h H) -> DefaultBodyViewModel<H> {
        let b: DefaultBody = Default::default();
        ViewModel {
            header,
            body: &b,
        }
    }
}

impl<'h, 'b, H, B> Default for ViewModel<'h, 'b, H, B>
where
    H: Serialize + Default + 'h,
    B: Serialize + Default + 'b,
{
    fn default() -> ViewModel<'h, 'b, H, B> {
        let h: H = Default::default();
        let b: B = Default::default();
        ViewModel::new(&h, &b)
    }
}

And I got this error:

error[E0597]: `h` does not live long enough
  --> server/src/view_models/mod.rs:61:22
   |
61 |             header: &h,
   |                      ^ borrowed value does not live long enough
...
64 |     }
   |     - borrowed value only lives until here

I know the borrowed value doesn’t live long enough, but is there a workaround to initialize something like this?


#2

A reference is only borrowing a value that something else is owning. Your new default H and B values need an owner, and it’s not the ViewModel (it only has references). This makes it impossible to implement Default for ViewModel in this way; there is no way to set up an owner to borrow from inside the default method.

It is the exception and not the rule when we can implement Default for a reference type or a type that contains references. There exists examples – for example &[T], the slice has a default, because an empty slice literal is a reference to a static but zero length part of memory.


#3

The workaround that I can think of (that doesn’t change the ownership semantics) is to use Option<&H> etc in the view struct. Then you can use a Default that uses None for these fields (but all methods need to adapt to handle this case).


#4

Yeah, that’s what I’m thinking as well, serde do handle Option nicely.


#5

Why do you force the ViewModel to have references? Perhaps you can leave it fully generic and allow owned or borrowed types to be used.


#6

@vitalyd is there a hint on how to do that?
Using std::borrow::Borrow as trait bound would mean every struct needs to implement that, right?
I’m trying to avoid complication and make it easier for later on.


#7

Oh I just meant:

struct ViewModel<H, B> {
    header: H,
    body: B,
}

Then it can be used with references or owned values, just like say you can have HashMap<String, String> or HashMap<&String, &String>.

Edit: So to elaborate a bit, here’s what I had in mind: Playground

Also, I’m not entirely sure if the DefaultHeader and DefaultBody will always have that literal text or not, but if they will, you should use something other than String (e.g. &'static str) to avoid the allocation. If sometimes it’ll have a constant/literal string and other times it’ll be something dynamic, you can store them in Cow<'static, str>.