Creating a vector of structs

I need to create a vector where each element is a struct. (I have several questions on this topic, so please hang in there with me.) To experiment I created a sample struct with one field. It looks like this:

struct Royalty {
    field: & str,
}

The compiler tells me that I need to create a lifetime parameter for this struct, and I'm clueless as to why that is necessary. Can anyone enlighten me? Thanks. Here's what it looks like when I add the lifetime parameter:

struct Royalty<'a> {
    field: &'a str,
}

Why shouldn't it be necessary?

References always have an associated lifetime parameter, but it's often elided (in function signatures) or inferred (in running code). Struct definitions can't take advantage of either of these mechanisms, so you have to specify the lifetime parameter explicitly. There's two ways to do that:

  1. Add a generic lifetime parameter to the struct, and use that lifetime in the reference, or
  2. Specify the reference lifetime to be 'static, which is the only lifetime annotation that has a meaning not derived from generic parameters.
2 Likes

You probably want

struct Royalty {
    field: String,
}

Structs with lifetime parameters are fairly niche

8 Likes

Ok, so I took @semicoleon 's advice and changed it from &str to String and the compiler accepted it. I'll try to stay away from using references in my structs from now on. Here's my new struct:

struct Royalty {
    field: String,
}

Now the compiler wants me to add #[derive(Debug)] above the struct definition. Like this:

#[derive(Debug)]
struct Royalty {
    field: String,
}

To be honest, I am completely clueless about what #[derive(Debug)] does for me or why I need it. Could someone explain why I need it? Thanks.

That is literally one Google search away. You are probably using your struct in some context where you are asking it to be debug-printed.

2 Likes

Rust, contrary to other programming languages, makes no assumptions on what traits should be auto-implemented for user-defined data structures.

This is because Rust is a so-called low-level language, and must be able to compile your program for environments where every tiny bit of memory matters.

1 Like

And, much more importantly, because:

  1. Auto-implementing certain traits would cause unsoundness (eg. Copy)
  2. Relatedly, not seeing traits explicitly in the source is not great (for most traits).
4 Likes

Totally. Observing unexpected behaviour because of some "magic" coming from an auto-trait implementation from the language is one of the ugliest things to debug.

1 Like

Ok, so the reason the compiler wanted me to add #[derive(Debug)] was that later on in the code I use {:?} in a println!() statement. What I'm wondering about now is why I haven't seen this before. I've used println!({:?}) many times and never needed to add #[derive(Debug)].

Now I have another mystery. Here's the complete code of my little experimental program:

#[derive(Debug)]
struct Royalty {
    field: String,
}

fn main() {
    let one = Royalty {
        field: "Kid King".to_string(),
    };

    let two = Royalty {
        field: "Nice Queen".to_string(),
    };

    let thevec = vec![one, two];

    println!("\n {:?} \n", thevec);
}

When I compile it I get the following warning:

warning: field `field` is never read
 --> src/main.rs:3:5
  |
2 | struct Royalty {
  |        ------- field in this struct
3 |     field: String,

Yet, the field is used in lines 8,12, & 17.
Any insights?

1 Like

The full error message states

note: Royalty has a derived impl for the trait Debug, but this is intentionally ignored during dead code analysis
= note: #[warn(dead_code)] on by default

And on line 8,12 you are not reading the field

You must have been printing values of types that already implemented Debug. :? will never work for a struct or enum that you define that doesn't implement Debug.

Yet, the field is used in lines 8,12, & 17.

The field is written on lines 8 and 12. It is read for debug-printing inside the Debug implementation. None of those uses is a non-debugging read.

Derived Debug implementations are ignored for purposes of this lint because if they weren't, you would never get to learn about unnecessary fields except in types that don't #[derive(Debug)]. Debug doesn't count because it is for debugging; your application should not be relying on the presence of any text whatsoever in Debug output.

6 Likes

One can do this, it's just less efficient. More memory usage, more code to write, more code executing at runtime. As you've seen later, you also have to add .to_string() (compiler recommendation) or String::from() (Rust book recommendation) to convert static strings to heap-based strings.

An advice I was given many years ago: "Don't fight the language/framework." This advice has served me always well and here it means to just add these 'a and be done. Compiler wants it, so do it. Compiler knows better than me how Rust works best.

Type String is for strings changing at runtime.

Could you enlarge on that statement some? Thanks.

Not sure where I could enlarge on strings, which questions are open. If you didn't read at least the first few chapters of The Rust Programming Language, it's certainly a good idea to do so. Like every language, Rust is based on a number of ideas and one should know these ideas to write code which reasonably aligns with them. Chapter 4 uses string and String to explain Rust's memory model.

If you come from more script-type languages like Python or JavaScript: Rust cares a lot about where data is stored: program code (compiled binary), stack or heap. None of your code samples changes one of the strings, so using static strings is sufficient. Content of the string is stored in program memory, then, a variable pointing to that is stored on the stack. That's about the most efficient way imaginable to work with strings. "Efficient", like minimum of memory used, fewest CPU processing cycles.

Making a String from such a static string means a number of things:

  1. Memory gets allocated on the heap.
  2. Content of the string gets copied over into this allocated memory.
  3. Code tracks helper variables, like the length of the string.
  4. A pointer to that allocated memory goes onto the stack anyways.

All this is pointless if strings never change. Avoiding to run useless code is what makes Rust so fast.

So you are saying it is better to use an &str pointer with 'a designating a lifetime than to use String because it uses memory better? However, if I am going to be changing the contents of the string, then wouldn't String be the better choice?

Exactly.

To be more precise, changing a static string isn't possible, so there's no other option than to accept the lower efficiency and use a String.

String is an owned value. That includes mutability, certainly, but it isn't just mutability. I would argue that storing borrowed values in a struct is a great way to end up fighting the language, as you know have to prove to the borrow checker that your borrow does last long enough. Fighting the borrow checker is one of the things that puts people off of Rust, and in the vast majority of programs the performance hit from creating a copy is completely unnoticeable.

If you know in advance that the struct will be short lived, then it's certainly ideal[1] to avoid needing to make copies. If you don't know it will be short lived, it's almost always easier to start with an owned type. You can go back and change it to be a borrow once you have a clearer picture of how your program is going to work.


  1. and sometimes critical ↩ī¸Ž

5 Likes

@semicoleon @Traumflug You both are making a lot of sense and have helped clear up some of the confusion I've had concerning String and &str and when to use one over the other. Thanks.

Well, I use string references in many of my structs and can't remember a case where the borrow checker didn't like it. No surprise, as static strings live forever and the borrow checker knows this.