How can I implement Default for &Struct?

Viewed 2163

After reading the rust book many times I think I'm starting to get the gist of lifetimes, but for me, an additional problem is the syntax we need to use to declare them. I find it is really counterintuitive.

I've simplified a silly code of mine onto this pair of structs (which one referencing the other).

#[derive(Debug, Default)]
pub struct TestStructA {
    pub byte_1: u8,
    pub byte_2: u8,
    pub vector: Vec<u8>,
}

impl<'a> Default for &'a TestStructA {
    fn default() -> &'a TestStructA {
        &TestStructA { byte_1: 10, byte_2: 20, vector: 'a vec![1, 2, 3] }
    }
}

#[derive(Debug, Default)]
pub struct TestStructB<'a> {
    pub test_array: &'a [u8],
    pub t_a: &'a TestStructA,
}

If you copy and paste this isolated code onto a main.rs file and compile it, then you get the following error:

error: expected `while`, `for`, `loop` or `{` after a label
  --> src/main.rs:10:59
   |
10 |         &TestStructA { byte_1: 10, byte_2: 20, vector: 'a vec![1, 2, 3] }
   |                                                           ^^^ expected `while`, `for`, `loop` or `{` after a label

error: labeled expression must be followed by `:`
  --> src/main.rs:10:59
   |
10 |         &TestStructA { byte_1: 10, byte_2: 20, vector: 'a vec![1, 2, 3] }
   |                                                        ---^^^^^^^^^^^^^
   |                                                        | |
   |                                                        | help: add `:` after the label
   |                                                        the label
   |
   = note: labels are used before loops and blocks, allowing e.g., `break 'label` to them

If I don't annotate the vector parameter lifetime, I get the expected: "check your lifetimes boy" (which makes sense).

error[E0515]: cannot return reference to temporary value
  --> src/main.rs:10:9
   |
10 |         &TestStructA { byte_1: 10, byte_2: 20, vector: vec![1, 2, 3] }
   |         ^-------------------------------------------------------------
   |         ||
   |         |temporary value created here
   |         returns a reference to data owned by the current function

Of course, if I choose a more drastic solution as removing the "vector" attribute from my struct, as all other attributes are scalars, the code compiles. But I need my struct to have non-scalar data structures of some kind. I suspect I need some kind of lifetime labeling for my vector inside the Default initializer, but I'm not sure what

By the way, I think my TestStructB is also properly lifetime annotated, but maybe not. Does it look correct?

Finally, I got here because the compiler said I needed to declare a Default initializer for the referenced (&) version of my original struct. This original struct was already happily used in my program, but never referenced. Whenever I started using it in a &referenced fashion, it was Rust claiming it needs its Default initializer. The thing is I still somewhat think I've done something wrong when using my referenced Struct, and that there is a proper way to use &referenced (and lifetime annotated) Structs without Rust complaining about their (non existing) default initializer.

EDIT: @JohnKugelman is more than right, I've come onto this "aberration" after the typical newbie fight with rust compiler trying to overcome the mainly cryptic messages it yields.

My simplified original was this one:

#[derive(Debug, Default)]
pub struct TestStructA {
    pub byte_1: u8,
    pub byte_2: u8,
    pub vector: Vec<u8>,
}

#[derive(Debug, Default)]
pub struct TestStructB<'a> {
    pub test_array: &'a [u8],
    pub t_a: &'a TestStructA,
}

With this code, the error I get is:

error[E0277]: the trait bound `&TestStructA: Default` is not satisfied
  --> src/main.rs:11:5
   |
11 |     pub t_a: &'a TestStructA,
   |     ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Default` is not implemented for `&TestStructA`
   |
   = help: the following implementations were found:
             <TestStructA as Default>
   = note: required by `std::default::Default::default`
   = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)

Apparently rustc misled me into thinking I need a &Struct Default initializer? Can't say...

1 Answers

Your unusual requirement arises because you're trying to implement Default for a structure TestStructB containing a reference. Most of the time, Default is used on types that have all owned values or can be empty in some way.

In order to implement Default for a reference &'a TestStructA, you would have to have some constant or leaked value of the type that the Default::default implementation can return a reference to — basically there needs to be an &'static TestStructA.

This is simple to do if the vector is empty because Vec::new() is a const fn, so we can construct a full compile-time-constant instance of TestStructA:

impl<'a> Default for &'a TestStructA {
    fn default() -> &'a TestStructA {
        static VALUE: TestStructA = TestStructA {
            byte_1: 10,
            byte_2: 20,
            vector: Vec::new()
        };
        &VALUE
    }
}

If you want to have some data in the vector, then you would have to use a lazy-initialization mechanism like once_cell to be able to execute vec! to allocate the vector's contents, then return a reference to it.

use once_cell::sync::Lazy;

static DEFAULT_A: Lazy<TestStructA> = Lazy::new(|| {
    TestStructA {byte_1: 10, byte_2: 20, vector: vec![1, 2, 3]}
});

impl<'a> Default for &'a TestStructA {
    fn default() -> &'a TestStructA {
        &DEFAULT_A
    }
}

If you were to do this, I'd recommend also implementing Default for TestStructA, because it is strange to have Default for the reference but not the owned value.

impl Default for TestStructA {
    fn default() -> TestStructA {
        DEFAULT_A.clone()
    }
}

(This only works if TestStructA also implements Clone, but it probably should. If it shouldn't, then instead put the struct literal inside the TestStructA::default method and have DEFAULT_A just be defined as Lazy::new(TestStructA::default).)

However, all these details are only necessary because

  1. You're implementing Default for TestStructB,
  2. which always contains a reference to a TestStructA.

You should consider whether TestStructB actually needs this reference in the default case — if t_a has type Option<&'a TestStructA>, for example, then it can just default to None. I don't have enough information to say whether this is appropriate for your application — make your own choice based on what the exact purpose of these structures is.

Related