Creation of struct with lifetime

I have

use std::fmt::Display;

#[derive(Debug)]
enum B {
    Jake,
}

impl Display for B {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{:?}", self)
    }
}

struct A<'a> {
    a: &'a str,
    b: B,
}

fn from<'l>(b: B) -> A<'l> {
    A {
        a: b.to_string().as_str(),
        b: b,
    }
}

fn from_empty<'l>(b:B) -> A<'l> {
    A { a: "",  b }
}

fn create_a(b: B, s: &str) -> A {
    A { a: s, b }
}

fn main() {
    let a0 = from_empty(B::Jake);
    let a1 = from(B::Jake);
    let a2 = create_a(B::Jake, B::Jake.to_string().as_str());

}

Why is from causing an error but not from_empty? Does "" have an implicit static lifetime?

Is there a way of making a function like from, where a1.a is constructed from b.to_string()? Or do I always have to construct like for a2?

Thanks!

no, "" not special, it's just have a different type. any string literal will have the type &'static str, while b.to_string().as_str() first create an owned String than borrows it, but the created String is a temporary variable, the borrows can't satisfy the lifetime of the return type.

it is not possible without leaking memory, because the paramter of from() is B which doesn't contain any allocated memory for the string representation, but your return type A<'a> need to borrow the underlying str, which only exists when the temporary String value returned by to_string() is in scope. but the temporary String is dropped as soon as you return from from().

so either you re-design your type, or you accept the fact that each type from() is called, you leak some memory. see the String::leak() method. the equivalence in stable rust is first convert the String into a boxed str slice then leak the Box<str>.

A generic lifetime parameter is that – a parameter, ie. the caller chooses it.

In from_empty<'l>(), this works because whatever 'l is, the string literal's 'static lifetime will satisfy it (&'static str can be shortened to &'l str for any 'l), but it's kind of misleading, because it masks your misunderstanding.

from<'l>() doesn't work because there the lifetime is local to the function, and so it can't be arbitrarily chosen by the caller – it is determined by the function body, and there's no way to extend it. (In particular, the lifetime of the str you are creating is the lifetime of a temporary inside the function, because that's what .to_string() returns.)

No, that doesn't make any sense. You would be returning a dangling reference to a temporary that was already destroyed.

In Rust, references don't keep alive values; you don't use references to "return by reference". If you want to return a string created inside a function, then return a String by-value.

2 Likes

To elaborate a bit more, the common point between Rust references and those in garbage-collected other programming languages is that, as long as you have a reference, you know that the reference's target is valid and accessible. The mechanism that ensures this is very different between the two, though:

  • In a garbage-collected language, holding on to a reference will delay the destruction of the target until the reference is gone.
  • In Rust, destroying or moving a value will invalidate any references to it¹. Or, equivalently, holding a reference will forbid moving or destroying its target. Whichever way you look at it, this is checked at compile-time: Any use of an invalid reference (or move of a referenced value) will prevent the program from compiling.

There are lots of tradeoffs between these two approaches, but the most relevant one here is that Rust allows you to control where and when every value exists quite precisely. It also requires you to do this, which may feel overly pedantic if you're used to GC'd languages.


¹ The actual rules and mechanism are more complicated and nuanced, but this is a good mental model.

This all makes sense. Thanks.

I think I will redesign the type, since I only care some of the time if i'm using ´&str´ or ´String´. i.e. I want ´&str´ in one module and ´String´ in another. So I think I can use:

use std::fmt::Display;

#[derive(Debug, Copy, Clone)]
enum B {
    J,
}

impl Display for B {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{:?}",self )
    }
}

struct Example<T: Display> {
    s: T,
    b: B
}

fn a_from_b(b: B) -> Example<String> {
    Example {
        b,
        s: b.to_string()
    }
}

fn empty_a<'a>(b: B) -> Example<&'a str> {
    Example {
        b, s: ""
    }
}

fn a_from_b_str(b: B, s: &str) -> Example<&str> {
    Example {
        b, s
    }
}

fn main() {
    let a1 = a_from_b(B::J);
    let a2 = empty_a(B::J);
    
    let a3 = a_from_b_str(B::J, B::J.to_string().as_str());
}

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.